package org.etnaframework.module.base.utils;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * <pre>
 * 日期时间相关处理工具类，参考Joda-Time和jdk8的java.time包下相关类，具有更好的通用性
 *
 * 支持使用{@link DatetimeUtils}中的工具方法，对{@link Date}对象进行操作
 *
 * 相关工具方法分以下几个部分（其中的XXX为时间单位，例如Days/Weeks），都可以指定时区进行处理
 *
 * now/today            系统变量，表示当前时间的时间戳和当前日期的00:00:00，获取昨天的时间戳可使用DatetimeUtil.today().minusDays(1)
 *
 * isBefore/isAfter     时间先后比较
 *
 * parse                从表示时间的字符串解析出{@link Date}对象，在{@link Date}中则是通过构造方法进行初始化
 *
 * getXXX               提取日期时间中的值，如获取年份/月/时分秒等
 *
 * retainXXX            时间修剪，例如获取传入时刻所在月的第一天的00:00:00，获取所在时刻的那个小时的开始等
 *
 * format               格式化输出，一部分通用的格式可使用{@link Date}.DF_，在{@link Date}则是通过toString方法输出
 *
 * plusXXX/minusXXX     时间加减，返回加减后的新实例（不会更改原对象的值，就像{@link String}那样的机制），能自动判断并处理大小月，平年闰年的情况
 *
 * getXXXBetween        获取时间差，支持精确比对或按日期进行比对，例如两个时刻 2015-01-01 12:00:00和2015-02-01 11:00:00
 *                      精确比对的话，后者必须要>=2015-02-01 12:00:00才能算是和前者相差一个月
 *                      按日期比对的话，后者只需要>=>=2015-02-01 00:00:00就可以算是一个月了，适用一些只需要按日期比对判断的场景
 *                      大小月、平年闰年情况能自动判断处理
 * </pre>
 *
 * @author jasyaf
 * @since 2023-09-01
 */
public final class DatetimeUtils {

    public static final int MILLIS_PER_SECOND = 1000;

    public static final int MILLIS_PER_MINUTE = MILLIS_PER_SECOND * 60;

    public static final int MILLIS_PER_HOUR = MILLIS_PER_MINUTE * 60;

    public static final int MILLIS_PER_DAY = MILLIS_PER_HOUR * 24;

    public static final int MILLIS_PER_WEEK = MILLIS_PER_DAY * 7;

    /**
     * 时区GMT-12
     */
    public static final TimeZone GMT_W12 = TimeZone.getTimeZone("GMT-12");

    /**
     * 时区GMT-11
     */
    public static final TimeZone GMT_W11 = TimeZone.getTimeZone("GMT-11");

    /**
     * 时区GMT-10
     */
    public static final TimeZone GMT_W10 = TimeZone.getTimeZone("GMT-10");

    /**
     * 时区GMT-9
     */
    public static final TimeZone GMT_W09 = TimeZone.getTimeZone("GMT-9");

    /**
     * 时区GMT-8
     */
    public static final TimeZone GMT_W08 = TimeZone.getTimeZone("GMT-8");

    /**
     * 时区GMT-7
     */
    public static final TimeZone GMT_W07 = TimeZone.getTimeZone("GMT-7");

    /**
     * 时区GMT-6
     */
    public static final TimeZone GMT_W06 = TimeZone.getTimeZone("GMT-6");

    /**
     * 时区GMT-5
     */
    public static final TimeZone GMT_W05 = TimeZone.getTimeZone("GMT-5");

    /**
     * 时区GMT-4
     */
    public static final TimeZone GMT_W04 = TimeZone.getTimeZone("GMT-4");

    /**
     * 时区GMT-3
     */
    public static final TimeZone GMT_W03 = TimeZone.getTimeZone("GMT-3");

    /**
     * 时区GMT-2
     */
    public static final TimeZone GMT_W02 = TimeZone.getTimeZone("GMT-2");

    /**
     * 时区GMT-1
     */
    public static final TimeZone GMT_W01 = TimeZone.getTimeZone("GMT-1");

    /**
     * 时区GMT+0
     */
    public static final TimeZone GMT = TimeZone.getTimeZone("GMT+0");

    /**
     * 时区GMT+1
     */
    public static final TimeZone GMT_E01 = TimeZone.getTimeZone("GMT+1");

    /**
     * 时区GMT+2
     */
    public static final TimeZone GMT_E02 = TimeZone.getTimeZone("GMT+2");

    /**
     * 时区GMT+3
     */
    public static final TimeZone GMT_E03 = TimeZone.getTimeZone("GMT+3");

    /**
     * 时区GMT+4
     */
    public static final TimeZone GMT_E04 = TimeZone.getTimeZone("GMT+4");

    /**
     * 时区GMT+5
     */
    public static final TimeZone GMT_E05 = TimeZone.getTimeZone("GMT+5");

    /**
     * 时区GMT+6
     */
    public static final TimeZone GMT_E06 = TimeZone.getTimeZone("GMT+6");

    /**
     * 时区GMT+7
     */
    public static final TimeZone GMT_E07 = TimeZone.getTimeZone("GMT+7");

    /**
     * 时区GMT+8
     */
    public static final TimeZone GMT_E08 = TimeZone.getTimeZone("GMT+8");

    /**
     * 时区GMT+9
     */
    public static final TimeZone GMT_E09 = TimeZone.getTimeZone("GMT+9");

    /**
     * 时区GMT+10
     */
    public static final TimeZone GMT_E10 = TimeZone.getTimeZone("GMT+10");

    /**
     * 时区GMT+11
     */
    public static final TimeZone GMT_E11 = TimeZone.getTimeZone("GMT+11");

    /**
     * 时区GMT+12
     */
    public static final TimeZone GMT_E12 = TimeZone.getTimeZone("GMT+12");

    /**
     * yyyy-MM-dd'T'HH:mm:ss.SSS'Z' 例如:2017-06-01T06:13:22.717Z
     */
    public static final String DF_yyyy_MM_dd_T_HHmmss_SSSZ = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";

    /**
     * yyyy-MM-dd'T'HH:mm:ss.SSS 例如:2017-06-01T06:13:22.717
     */
    public static final String DF_yyyy_MM_dd_T_HHmmss_SSS = "yyyy-MM-dd'T'HH:mm:ss.SSS";

    /**
     * yyyy-MM-dd'T'HH:mm:ss'Z'
     */
    public static final String DF_yyyy_MM_dd_T_HHmmssZ = "yyyy-MM-dd'T'HH:mm:ssZ";

    /**
     * yyyy-MM-dd'T'HH:mm:ss 例如:2017-06-01T06:13:22
     */
    public static final String DF_yyyy_MM_dd_T_HHmmss = "yyyy-MM-dd'T'HH:mm:ss";

    /**
     * yyyy-MM-dd HH:mm:ss.SSSZ
     */
    public static final String DF_yyyy_MM_dd_HHmmss_SSSZ = "yyyy-MM-dd HH:mm:ss.SSSZ";

    /**
     * yyyy-MM-dd HH:mm:ss.SSS
     */
    public static final String DF_yyyy_MM_dd_HHmmss_SSS = "yyyy-MM-dd HH:mm:ss.SSS";

    /**
     * yyyy-MM-dd HH:mm:ss.SSS000 XXX
     */
    public static final String DF_yyyy_MM_dd_HHmmss_SSS000_XXX = "yyyy-MM-dd HH:mm:ss.SSS000 XXX";

    /**
     * yyyy-MM-dd HH:mm:ss
     */
    public static final String DF_yyyy_MM_dd_HHmmss = "yyyy-MM-dd HH:mm:ss";

    /**
     * yy-MM-dd HH:mm:ss
     */
    public static final String DF_yy_MM_dd_HHmmss = "yy-MM-dd HH:mm:ss";

    /**
     * yyyy-MM-dd HH:mm
     */
    public static final String DF_yyyy_MM_dd_HHmm = "yyyy-MM-dd HH:mm";

    /**
     * yyyyMMddHHmmssSSS
     */
    public static final String DF_yyyyMMddHHmmssSSS = "yyyyMMddHHmmssSSS";

    /**
     * yyyyMMddHHmmss
     */
    public static final String DF_yyyyMMddHHmmss = "yyyyMMddHHmmss";

    /**
     * yyyyMMddHHmm
     */
    public static final String DF_yyyyMMddHHmm = "yyyyMMddHHmm";

    /**
     * yyMMddHHmmss
     */
    public static final String DF_yyMMddHHmmss = "yyMMddHHmmss";

    /**
     * yyMMddHHmm
     */
    public static final String DF_yyMMddHHmm = "yyMMddHHmm";

    /**
     * yyMMddHHmm000000000
     */
    public static final String DF_yyMMddHHmm000000000 = "yyMMddHHmm000000000";

    /**
     * yyyy-MM-dd
     */
    public static final String DF_yyyy_MM_dd = "yyyy-MM-dd";

    /**
     * yyyyMMdd
     */
    public static final String DF_yyyyMMdd = "yyyyMMdd";

    /**
     * yyMMdd
     */
    public static final String DF_yyMMdd = "yyMMdd";

    /**
     * yyMM
     */
    public static final String DF_yyMM = "yyMM";

    /**
     * MMyy
     */
    public static final String DF_MMyy = "MMyy";

    /**
     * yyyyMM
     */
    public static final String DF_yyyyMM = "yyyyMM";

    /**
     * yyyy-MM
     */
    public static final String DF_yyyy_MM = "yyyy-MM";

    /**
     * yy-MM-dd
     */
    public static final String DF_yy_MM_dd = "yy-MM-dd";

    /**
     * MM-dd
     */
    public static final String DF_MM_dd = "MM-dd";

    /**
     * MM-dd HH:mm
     */
    public static final String DF_MM_dd_HH_mm = "MM-dd HH:mm";

    /**
     * HH:mm:ss.S
     */
    public static final String DF_HH_mm_ss_S = "HH:mm:ss.S";

    /**
     * HH:mm:ss
     */
    public static final String DF_HH_mm_ss = "HH:mm:ss";

    /**
     * HHmmss
     */
    public static final String DF_HHmmss = "HHmmss";

    /**
     * HH:mm
     */
    public static final String DF_HH_mm = "HH:mm";

    /**
     * HHmm
     */
    public static final String DF_HHmm = "HHmm";

    /**
     * 1 星期一
     */
    public static final int MONDAY = 1;

    /**
     * 2 星期二
     */
    public static final int TUESDAY = 2;

    /**
     * 3 星期三
     */
    public static final int WEDNESDAY = 3;

    /**
     * 4 星期四
     */
    public static final int THURSDAY = 4;

    /**
     * 5 星期五
     */
    public static final int FRIDAY = 5;

    /**
     * 6 星期六
     */
    public static final int SATURDAY = 6;

    /**
     * 7 星期日
     */
    public static final int SUNDAY = 7;

    /**
     * 1 一月
     */
    public static final int JANUARY = 1;

    /**
     * 2 二月
     */
    public static final int FEBRUARY = 2;

    /**
     * 3 三月
     */
    public static final int MARCH = 3;

    /**
     * 4 四月
     */
    public static final int APRIL = 4;

    /**
     * 5 五月
     */
    public static final int MAY = 5;

    /**
     * 6 六月
     */
    public static final int JUNE = 6;

    /**
     * 7 七月
     */
    public static final int JULY = 7;

    /**
     * 8 八月
     */
    public static final int AUGUST = 8;

    /**
     * 9 九月
     */
    public static final int SEPTEMBER = 9;

    /**
     * 10 十月
     */
    public static final int OCTOBER = 10;

    /**
     * 11 十一月
     */
    public static final int NOVEMBER = 11;

    /**
     * 12 十二月
     */
    public static final int DECEMBER = 12;

    /**
     * 每个月的天数（平年），下标说明：1月份的下标是1，2月份的下标是2
     */
    public static final int[] DAYS_OF_MONTH = {
        0,
        31,
        28,
        31,
        30,
        31,
        30,
        31,
        31,
        30,
        31,
        30,
        31
    };

    /**
     * 日期格式->DateFormat映射
     */
    private static final Map<String, ThreadLocal<DateFormat>> _dateFormatFactory = new ConcurrentHashMap<>();

    /**
     * 默认时间格式
     */
    static String defaultDateTimeFormat = DF_yyyy_MM_dd_HHmmss;

    /**
     * 默认日期格式
     */
    static String defaultDateFormat = DF_yyyy_MM_dd;

    private static Date testNow;

    private DatetimeUtils() {
    }

    /**
     * 设置在单元测试中{@link #now()}的返回值，用于在测试中变造测试时间
     */
    public static void setTestNow(String testNow) {
        Date tn = parseSmartly(testNow);
        if (null != tn) {
            setTestNow(tn);
        }
    }

    /**
     * 设置在单元测试中{@link #now()}的返回值，用于在测试中变造测试时间
     */
    public static void setTestNow(Date testNow) {
        System.err.println("当前系统时间被模拟设置为" + testNow);
        DatetimeUtils.testNow = testNow;
    }

    /**
     * 将单元测试中{@link #now()}的变造时间恢复到自定义的当前时间
     */
    public static void resetTestNow() {
        DatetimeUtils.testNow = null;
    }

    public static String getDefaultDatetimeFormat() {
        return defaultDateTimeFormat;
    }

    public static String getDefaultDateFormat() {
        return defaultDateFormat;
    }

    public static void setDefaultDateTimeFormat(String defaultDateTimeFormat) {
        DatetimeUtils.defaultDateTimeFormat = defaultDateTimeFormat;
    }

    public static void setDefaultDateFormat(String defaultDateFormat) {
        DatetimeUtils.defaultDateFormat = defaultDateFormat;
    }

    /**
     * 注意{@link DateFormat}不是线程安全的，在多线程环境下需要做同步处理，这里使用{@link ThreadLocal}让每个线程一个实例避免并发问题
     */
    private static ThreadLocal<DateFormat> _prepareDateFormatPerThread(final String format) {
        return new ThreadLocal<DateFormat>() {

            @Override
            protected synchronized DateFormat initialValue() {
                try {
                    DateFormat df = new SimpleDateFormat(format, Locale.getDefault());
                    df.setLenient(false);
                    return df;
                } catch (Exception e) {
                    return null;
                }
            }
        };
    }

    /**
     * 获得线程安全的{@link DateFormat}（注意由于设置了时区，为了保证不出问题，请每次都要重新获取之）
     *
     * @param tz 如果为null表示使用当前系统默认的时区
     */
    static DateFormat _getDateFormat(String format, TimeZone tz) {
        ThreadLocal<DateFormat> tdf = _dateFormatFactory.get(format);
        if (null == tdf) {
            tdf = _prepareDateFormatPerThread(format);
            _dateFormatFactory.put(format, tdf);
        }
        DateFormat df = tdf.get();
        if (null == df) { // 如果获取到null，就新建之
            df = new SimpleDateFormat(format, Locale.getDefault());
        }
        if (tz != null) {
            df.setTimeZone(tz);
        } else {
            df.setTimeZone(TimeZone.getDefault());
        }
        return df;
    }

    /**
     * 从UNIX时间戳转换
     */
    public static Date fromUnixTime(long unixTimeSecond) {
        return new Date(unixTimeSecond * MILLIS_PER_SECOND);
    }

    /**
     * <pre>
     * 获取当前时刻的{@link Date}，包含时分秒部分
     *
     * 请注意，Date对象中只保存了GMT时间戳，即1970-01-01T00:00:00+00:00到当前的毫秒数
     * 本身不包含时区信息，时区需要转出来的时候才能指定
     * </pre>
     */
    public static Date now() {
        if (null != testNow) {
            return testNow;
        }
        return new Date();
    }

    /**
     * 获取当前时刻的字符串，默认格式为{@link #defaultDateTimeFormat}，使用当前系统默认的时区
     */
    public static String nowString() {
        return format(now());
    }

    /**
     * 获取当前时刻的字符串，使用指定的格式，使用当前系统默认的时区
     */
    public static String nowString(String format) {
        return format(now(), format);
    }

    /**
     * 获取当前时刻的字符串，默认格式为{@link #defaultDateTimeFormat}，使用指定的时区
     */
    public static String nowString(TimeZone tz) {
        return format(now(), tz);
    }

    /**
     * 获取当前时刻的字符串，使用指定的格式和时区
     */
    public static String nowString(String format, TimeZone tz) {
        return format(now(), format, tz);
    }

    /**
     * 获取当前系统默认的时区的当天日期{@link Date}，时分秒部分为当天的00:00:00
     */
    public static Date today() {
        if (null != testNow) {
            return retainDay(testNow);
        }
        Calendar cal = Calendar.getInstance(TimeZone.getDefault());
        cal.setLenient(false);
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);
        return cal.getTime();
    }

    /**
     * 获取当前系统默认的时区的当天日期字符串，默认格式为{@link #defaultDateFormat}
     */
    public static String todayString() {
        return formatDate(today());
    }

    /**
     * 获取当前系统默认的时区的当天日期字符串，使用指定的格式
     */
    public static String todayString(String format) {
        return format(today(), format);
    }

    /**
     * 获取当前系统默认的时区的昨天日期{@link Date}，时分秒部分为当天的00:00:00
     */
    public static Date yesterday() {
        return minusDays(today(), 1);
    }

    /**
     * 获取当前系统默认的时区的昨天日期字符串，默认格式为{@link #defaultDateFormat}
     */
    public static String yesterdayString() {
        return format(yesterday());
    }

    /**
     * 获取当前系统默认的时区的昨天日期字符串，使用指定的格式
     */
    public static String yesterdayString(String format) {
        return format(yesterday(), format);
    }

    /**
     * 获取当前系统默认的时区的明天日期{@link Date}，时分秒部分为当天的00:00:00
     */
    public static Date tomorrow() {
        return plusDays(today(), 1);
    }

    /**
     * 获取当前系统默认的时区的明天日期字符串，默认格式为{@link #defaultDateFormat}
     */
    public static String tomorrowString() {
        return format(tomorrow());
    }

    /**
     * 获取当前系统默认的时区的明天日期字符串，使用指定的格式
     */
    public static String tomorrowString(String format) {
        return format(tomorrow(), format);
    }

    /**
     * 判断before代表时刻是否在after之前
     */
    public static boolean isBefore(Date before, Date after) {
        return before.getTime() < after.getTime();
    }

    /**
     * 判断date代表时刻是否在当前时刻之前
     */
    public static boolean isBeforeNow(Date date) {
        return date.getTime() < now().getTime();
    }

    /**
     * 判断before代表时刻是否在after之后
     */
    public static boolean isAfter(Date before, Date after) {
        return before.getTime() > after.getTime();
    }

    /**
     * 判断date代表时刻是否在当前时刻之后
     */
    public static boolean isAfterNow(Date date) {
        return date.getTime() > now().getTime();
    }

    /**
     * 判断before代表时刻是否在after之前
     *
     * @param before/after 如果解析失败,直接返回false
     */
    public static boolean isBefore(String before, String after) {
        Date _before = parseSmartly(before);
        Date _after = parseSmartly(after);
        return !(null == _before || null == _after) && _before.getTime() < _after.getTime();
    }

    /**
     * 获取当前日期是星期几
     *
     * @return 当前日期是星期几
     */
    public static String getWeekOfDate(Date dt) {
        String[] weekDays = {
            "星期日",
            "星期一",
            "星期二",
            "星期三",
            "星期四",
            "星期五",
            "星期六"
        };
        Calendar cal = Calendar.getInstance();
        cal.setTime(dt);
        int w = cal.get(Calendar.DAY_OF_WEEK) - 1;
        if (w < 0) {
            w = 0;
        }
        return weekDays[w];
    }

    /**
     * 判断date代表时刻是否在当前时刻之前
     *
     * @param date 如果解析失败,直接返回false
     */
    public static boolean isBeforeNow(String date) {
        Date _date = parseSmartly(date);
        return null != _date && _date.getTime() < now().getTime();
    }

    /**
     * 判断before代表时刻是否在after之后
     *
     * @param before/after 如果解析失败,直接返回false
     */
    public static boolean isAfter(String before, String after) {
        Date _before = parseSmartly(before);
        Date _after = parseSmartly(after);
        return !(null == _before || null == _after) && _before.getTime() > _after.getTime();
    }

    /**
     * 判断date代表时刻是否在当前时刻之后
     *
     * @param date 如果解析失败,直接返回false
     */
    public static boolean isAfterNow(String date) {
        Date _date = parseSmartly(date);
        return null != _date && _date.getTime() > now().getTime();
    }

    /**
     * 将字符串的时间转换为{@link Date}，默认格式为{@link #defaultDateTimeFormat}，使用当前系统默认的时区
     * 支持智能判断时间格式，提高兼容性
     *
     * @return 如果转换过程中出现异常将返回null
     */
    public static Date parseSmartly(String datetime) {
        return parseSmartly(datetime, null);
    }

    /**
     * 将字符串的时间转换为{@link Date}，默认格式为{@link #defaultDateTimeFormat}，使用指定的时区
     * 支持智能判断时间格式，提高兼容性
     *
     * @return 如果转换过程中出现异常将返回null
     */
    public static Date parseSmartly(String date, TimeZone tz) {
        if (null == date || date.isEmpty()) {
            return null;
        }
        Date result = null;
        try {
            result = _getDateFormat(defaultDateTimeFormat, tz).parse(date);
        } catch (Exception ignore) {
        }
        if (null == result) {
            // 如果传入的是date的格式，则按date格式解析，增强兼容性
            if (date.length() == defaultDateFormat.length()) {
                try {
                    return _getDateFormat(defaultDateFormat, tz).parse(date);
                } catch (Exception ignore) {
                }
            }
            if (date.length() == defaultDateTimeFormat.length()) {
                try {
                    return _getDateFormat(defaultDateTimeFormat, tz).parse(date);
                } catch (Exception ignore) {
                }
            }
            // 特殊判断
            if (date.length() == DF_yyyyMMdd.length()) {
                try {
                    return _getDateFormat(DF_yyyyMMdd, tz).parse(date);
                } catch (Exception ignore) {
                }
            }
            if (date.contains("-")) {
                if (date.length() == DF_yyyy_MM_dd_HHmmss.length()) {
                    try {
                        return _getDateFormat(DF_yyyy_MM_dd_HHmmss, tz).parse(date);
                    } catch (Exception ignore) {
                    }
                }
                if (date.length() == DF_yyyy_MM_dd_HHmm.length()) {
                    try {
                        return _getDateFormat(DF_yyyy_MM_dd_HHmm, tz).parse(date);
                    } catch (Exception ignore) {
                    }
                }
                if (date.length() == DF_yyyy_MM_dd.length()) {
                    try {
                        return _getDateFormat(DF_yyyy_MM_dd, tz).parse(date);
                    } catch (Exception ignore) {
                    }
                }
            }
            if (date.contains("/")) {
                try {
                    return _getDateFormat("yyyy/MM/dd HH:mm:ss", tz).parse(date);
                } catch (Exception ignore) {
                }
                try {
                    return _getDateFormat("yyyy/MM/dd HH:mm", tz).parse(date);
                } catch (Exception ignore) {
                }
                try {
                    return _getDateFormat("yyyy/MM/dd HH", tz).parse(date);
                } catch (Exception ignore) {
                }
                try {
                    return _getDateFormat("yyyy/MM/dd", tz).parse(date);
                } catch (Exception ignore) {
                }
            }
            if (date.contains("年")) {
                try {
                    return _getDateFormat("yyyy年MM月dd日HH:mm:ss", tz).parse(date);
                } catch (Exception ignore) {
                }
                try {
                    return _getDateFormat("yyyy年MM月dd日HH:mm", tz).parse(date);
                } catch (Exception ignore) {
                }
                try {
                    return _getDateFormat("yyyy年MM月dd日HH", tz).parse(date);
                } catch (Exception ignore) {
                }
                try {
                    return _getDateFormat("yyyy年MM月dd日", tz).parse(date);
                } catch (Exception ignore) {
                }
            }
            if (date.contains("T")) {
                try {
                    return _getDateFormat(DF_yyyy_MM_dd_T_HHmmss_SSSZ, tz).parse(date);
                } catch (Exception ignore) {
                }
                try {
                    return _getDateFormat(DF_yyyy_MM_dd_T_HHmmss_SSS, tz).parse(date);
                } catch (Exception ignore) {
                }
                try {
                    return _getDateFormat(DF_yyyy_MM_dd_T_HHmmssZ, tz).parse(date);
                } catch (Exception ignore) {
                }
                try {
                    return _getDateFormat(DF_yyyy_MM_dd_T_HHmmss, tz).parse(date);
                } catch (Exception ignore) {
                }
            }
            if (date.contains("UTC")) {
                try {
                    return _getDateFormat("yyyy-MM-dd HH:mm:ss 'UTC'", tz).parse(date);
                } catch (Exception ignore) {
                }
            }

            if (date.contains("CST")) {
                try {
                    return _getDateFormat("yyyy-MM-dd HH:mm:ss 'CST'", tz).parse(date);
                } catch (Exception ignore) {
                }
            }
        }
        return result;
    }

    /**
     * 将字符串的时间转换为{@link Date}，使用当前系统默认的时区
     *
     * @return 如果转换过程中出现异常将返回null
     */
    public static Date parse(String date, String format) {
        try {
            return null != date ? _getDateFormat(format, null).parse(date) : null;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 将字符串的时间转换为{@link Date}，如果转换过程中出现异常将返回null，使用指定的格式和时区
     *
     * @return 如果转换过程中出现异常将返回null
     */
    public static Date parse(String date, String format, TimeZone tz) {
        try {
            return null != date ? _getDateFormat(format, tz).parse(date) : null;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 将字符串的日期转换为{@link Date}，默认格式为yyyy-MM-dd，默认输出格式可在{@link DatetimeUtils}中指定，使用当前系统默认的时区
     *
     * @return 如果转换过程中出现异常将返回null
     */
    public static Date parseDate(String date) {
        try {
            return null != date ? _getDateFormat(defaultDateFormat, null).parse(date) : null;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 将字符串的日期转换为{@link Date}，默认格式为yyyy-MM-dd，默认输出格式可在{@link DatetimeUtils}中指定，使用指定的时区
     *
     * @return 如果转换过程中出现异常将返回null
     */
    public static Date parseDate(String date, TimeZone tz) {
        try {
            return null != date ? _getDateFormat(defaultDateFormat, tz).parse(date) : null;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 获取指定毫秒时间戳对应时刻的【毫秒】，如12:30:45.123，返回123
     *
     * @return 返回范围0-999
     */
    public static int getMillisOfSecond(long timestamp) {
        Calendar cal = Calendar.getInstance();
        cal.setLenient(false);
        cal.setTimeInMillis(timestamp);
        return cal.get(Calendar.MILLISECOND);
    }

    /**
     * 获取指定毫秒时间戳对应时刻的【秒】，如12:30:45，返回45
     *
     * @return 返回范围0-59
     */
    public static int getSecondOfMinute(long timestamp) {
        Calendar cal = Calendar.getInstance();
        cal.setLenient(false);
        cal.setTimeInMillis(timestamp);
        return cal.get(Calendar.SECOND);
    }

    // 提取日期时间中的值

    /**
     * 获取指定毫秒时间戳对应时刻的【分】，如12:30:45，返回30，使用系统默认的时区
     *
     * @return 返回范围0-59
     */
    public static int getMinuteOfHour(long timestamp) {
        return getMinuteOfHour(timestamp, TimeZone.getDefault());
    }

    /**
     * 获取指定毫秒时间戳对应时刻的【分】，如12:30:45，返回30，使用指定的时区
     *
     * @return 返回范围0-59
     */
    public static int getMinuteOfHour(long timestamp, TimeZone tz) {
        Calendar cal = Calendar.getInstance(tz);
        cal.setLenient(false);
        cal.setTimeInMillis(timestamp);
        return cal.get(Calendar.MINUTE);
    }

    /**
     * 获取指定毫秒时间戳对应时刻的【时】，如12:30:45，返回12，使用系统默认的时区
     *
     * @return 返回范围0-23
     */
    public static int getHourOfDay(long timestamp) {
        return getHourOfDay(timestamp, TimeZone.getDefault());
    }

    /**
     * 获取指定毫秒时间戳对应时刻的【时】，如12:30:45，返回12，使用指定的时区
     *
     * @return 返回范围0-59
     */
    public static int getHourOfDay(long timestamp, TimeZone tz) {
        Calendar cal = Calendar.getInstance(tz);
        cal.setLenient(false);
        cal.setTimeInMillis(timestamp);
        return cal.get(Calendar.HOUR_OF_DAY);
    }

    /**
     * 获取指定毫秒时间戳对应日期是星期几，星期日为7，星期一到六为1-6，使用系统默认的时区
     *
     * @return 返回范围1-7 对应星期一到星期日，可使用{@link #MONDAY}到{@link #SUNDAY}替代
     */
    public static int getDayOfWeek(long timestamp) {
        return getDayOfWeek(timestamp, TimeZone.getDefault());
    }

    /**
     * 获取指定毫秒时间戳对应日期是星期几，星期日为7，星期一到六为1-6，使用指定的时区
     *
     * @return 返回范围1-7 对应星期一到星期日，可使用{@link #MONDAY}到{@link #SUNDAY}替代
     */
    public static int getDayOfWeek(long timestamp, TimeZone tz) {
        Calendar cal = Calendar.getInstance(tz);
        cal.setLenient(false);
        cal.setTimeInMillis(timestamp);
        int day = cal.get(Calendar.DAY_OF_WEEK) - 1;
        return day == 0 ? SUNDAY : day;
    }

    /**
     * 获取指定毫秒时间戳对应的月份的日期，如2月1日，就返回1，使用系统默认的时区
     *
     * @return 返回范围1-31，最大为当月的最后一天的数值
     */
    public static int getDayOfMonth(long timestamp) {
        return getDayOfMonth(timestamp, TimeZone.getDefault());
    }

    /**
     * 获取指定毫秒时间戳对应的月份的日期，如2月1日，就返回1，使用指定的时区
     *
     * @return 返回范围1-31，最大为当月的最后一天的数值
     */
    public static int getDayOfMonth(long timestamp, TimeZone tz) {
        Calendar cal = Calendar.getInstance(tz);
        cal.setLenient(false);
        cal.setTimeInMillis(timestamp);
        return cal.get(Calendar.DAY_OF_MONTH);
    }

    /**
     * 获取指定毫秒时间戳对应日期是这一年的第几天，使用系统默认的时区
     *
     * @return 返回范围平年1-365，闰年1-366
     */
    public static int getDayOfYear(long timestamp) {
        return getDayOfYear(timestamp, TimeZone.getDefault());
    }

    /**
     * 获取指定毫秒时间戳对应日期是这一年的第几天，使用指定的时区
     *
     * @return 返回范围平年1-365，闰年1-366
     */
    public static int getDayOfYear(long timestamp, TimeZone tz) {
        Calendar cal = Calendar.getInstance(tz);
        cal.setLenient(false);
        cal.setTimeInMillis(timestamp);
        return cal.get(Calendar.DAY_OF_YEAR);
    }

    /**
     * 获取指定毫秒时间戳对应日期的【月】，使用系统默认的时区
     *
     * @return 返回范围1-12，可使用{@link Date}的JANUARY - DECEMBER来指代
     */
    public static int getMonthOfYear(long timestamp) {
        return getMonthOfYear(timestamp, TimeZone.getDefault());
    }

    /**
     * 获取指定毫秒时间戳对应日期的【月】，使用指定的时区
     *
     * @return 返回范围1-12，可使用{@link Date}的JANUARY - DECEMBER来指代
     */
    public static int getMonthOfYear(long timestamp, TimeZone tz) {
        Calendar cal = Calendar.getInstance(tz);
        cal.setLenient(false);
        cal.setTimeInMillis(timestamp);
        return cal.get(Calendar.MONTH) + 1;
    }

    /**
     * 获取指定毫秒时间戳对应日期的【年】，公元前的时间，例如221BC将表示为-221，使用系统默认的时区
     */
    public static int getYear(long timestamp) {
        return getYear(timestamp, TimeZone.getDefault());
    }

    /**
     * 获取指定毫秒时间戳对应日期的【年】，公元前的时间，例如221BC将表示为-221，使用指定的时区
     */
    public static int getYear(long timestamp, TimeZone tz) {
        Calendar cal = Calendar.getInstance(tz);
        cal.setLenient(false);
        cal.setTimeInMillis(timestamp);
        if (cal.get(Calendar.ERA) == GregorianCalendar.BC) {
            return -cal.get(Calendar.YEAR);
        }
        return cal.get(Calendar.YEAR);
    }

    /**
     * 获取指定{@link Date}对应时刻的【毫秒】，如12:30:45.123，返回123
     *
     * @return 返回范围0-999
     */
    public static int getMillisOfSecond(Date date) {
        return getMillisOfSecond(date.getTime());
    }

    /**
     * 获取指定{@link Date}对应时刻的【秒】，如12:30:45，返回45
     *
     * @return 返回范围0-59
     */
    public static int getSecondOfMinute(Date date) {
        return getSecondOfMinute(date.getTime());
    }

    /**
     * 获取指定{@link Date}对应时刻的【分】，如12:30:45，返回30，使用系统默认的时区
     *
     * @return 返回范围0-59
     */
    public static int getMinuteOfHour(Date date) {
        return getMinuteOfHour(date.getTime());
    }

    /**
     * 获取指定{@link Date}对应时刻的【分】，如12:30:45，返回30，使用指定的时区
     *
     * @return 返回范围0-59
     */
    public static int getMinuteOfHour(Date date, TimeZone tz) {
        return getMinuteOfHour(date.getTime(), tz);
    }

    /**
     * 获取指定{@link Date}对应时刻的【时】，如12:30:45，返回12，使用系统默认的时区
     *
     * @return 返回范围0-23
     */
    public static int getHourOfDay(Date date) {
        return getHourOfDay(date.getTime());
    }

    /**
     * 获取指定{@link Date}对应时刻的【时】，如12:30:45，返回12，使用指定的时区
     *
     * @return 返回范围0-59
     */
    public static int getHourOfDay(Date date, TimeZone tz) {
        return getHourOfDay(date.getTime(), tz);
    }

    /**
     * 获取指定{@link Date}对应日期是星期几，星期日为7，星期一到六为1-6，使用系统默认的时区
     *
     * @return 返回范围1-7 对应星期一到星期日，可使用{@link #MONDAY}到{@link #SUNDAY}替代
     */
    public static int getDayOfWeek(Date date) {
        return getDayOfWeek(date.getTime());
    }

    /**
     * 获取指定{@link Date}对应日期是星期几，星期日为7，星期一到六为1-6，使用指定的时区
     *
     * @return 返回范围1-7 对应星期一到星期日，可使用{@link #MONDAY}到{@link #SUNDAY}替代
     */
    public static int getDayOfWeek(Date date, TimeZone tz) {
        return getDayOfWeek(date.getTime(), tz);
    }

    /**
     * 获取指定{@link Date}对应的月份的日期，如2月1日，就返回1，使用系统默认的时区
     *
     * @return 返回范围1-31，最大为当月的最后一天的数值
     */
    public static int getDayOfMonth(Date date) {
        return getDayOfMonth(date.getTime());
    }

    /**
     * 获取指定{@link Date}对应的月份的日期，如2月1日，就返回1，使用指定的时区
     *
     * @return 返回范围1-31，最大为当月的最后一天的数值
     */
    public static int getDayOfMonth(Date date, TimeZone tz) {
        return getDayOfMonth(date.getTime(), tz);
    }

    /**
     * 获取指定{@link Date}对应日期是这一年的第几天，使用系统默认的时区
     *
     * @return 返回范围平年1-365，闰年1-366
     */
    public static int getDayOfYear(Date date) {
        return getDayOfYear(date.getTime());
    }

    /**
     * 获取指定{@link Date}对应日期是这一年的第几天，使用指定的时区
     *
     * @return 返回范围平年1-365，闰年1-366
     */
    public static int getDayOfYear(Date date, TimeZone tz) {
        return getDayOfYear(date.getTime(), tz);
    }

    /**
     * 获取指定{@link Date}对应日期的【月】，使用系统默认的时区
     *
     * @return 返回范围1-12，可使用{@link Date}的JANUARY - DECEMBER来指代
     */
    public static int getMonthOfYear(Date date) {
        return getMonthOfYear(date.getTime());
    }

    /**
     * 获取指定{@link Date}对应日期的【月】，使用指定的时区
     *
     * @return 返回范围1-12，可使用{@link Date}的JANUARY - DECEMBER来指代
     */
    public static int getMonthOfYear(Date date, TimeZone tz) {
        return getMonthOfYear(date.getTime(), tz);
    }

    /**
     * 获取指定{@link Date}对应日期的【年】，公元前的时间，例如221BC将表示为-221，使用系统默认的时区
     */
    public static int getYear(Date date) {
        return getYear(date.getTime());
    }

    /**
     * 获取指定{@link Date}对应日期的【年】，公元前的时间，例如221BC将表示为-221，使用指定的时区
     */
    public static int getYear(Date date, TimeZone tz) {
        return getYear(date.getTime(), tz);
    }

    /**
     * 获取指定{@link Date}对应时刻的【毫秒】，如12:30:45.123，返回123
     *
     * @param date 使用{@link #parseSmartly(String)}智能解析时间，如果解析失败返回{@link Integer#MIN_VALUE}
     * @return 返回范围0-999
     */
    public static int getMillisOfSecond(String date) {
        Date _date = parseSmartly(date);
        return _date == null ? Integer.MIN_VALUE : getMillisOfSecond(_date.getTime());
    }

    /**
     * 获取指定{@link Date}对应时刻的【秒】，如12:30:45，返回45
     *
     * @param date 使用{@link #parseSmartly(String)}智能解析时间，如果解析失败返回{@link Integer#MIN_VALUE}
     * @return 返回范围0-59
     */
    public static int getSecondOfMinute(String date) {
        Date _date = parseSmartly(date);
        return _date == null ? Integer.MIN_VALUE : getSecondOfMinute(_date.getTime());
    }

    /**
     * 获取指定{@link Date}对应时刻的【分】，如12:30:45，返回30，使用系统默认的时区
     *
     * @param date 使用{@link #parseSmartly(String)}智能解析时间，如果解析失败返回{@link Integer#MIN_VALUE}
     * @return 返回范围0-59
     */
    public static int getMinuteOfHour(String date) {
        Date _date = parseSmartly(date);
        return _date == null ? Integer.MIN_VALUE : getMinuteOfHour(_date.getTime());
    }

    /**
     * 获取指定{@link Date}对应时刻的【分】，如12:30:45，返回30，使用指定的时区
     *
     * @param date 使用{@link #parseSmartly(String)}智能解析时间，如果解析失败返回{@link Integer#MIN_VALUE}
     * @return 返回范围0-59
     */
    public static int getMinuteOfHour(String date, TimeZone tz) {
        Date _date = parseSmartly(date);
        return _date == null ? Integer.MIN_VALUE : getMinuteOfHour(_date.getTime(), tz);
    }

    /**
     * 获取指定{@link Date}对应时刻的【时】，如12:30:45，返回12，使用系统默认的时区
     *
     * @param date 使用{@link #parseSmartly(String)}智能解析时间，如果解析失败返回{@link Integer#MIN_VALUE}
     * @return 返回范围0-23
     */
    public static int getHourOfDay(String date) {
        Date _date = parseSmartly(date);
        return _date == null ? Integer.MIN_VALUE : getHourOfDay(_date.getTime());
    }

    /**
     * 获取指定{@link Date}对应时刻的【时】，如12:30:45，返回12，使用指定的时区
     *
     * @param date 使用{@link #parseSmartly(String)}智能解析时间，如果解析失败返回{@link Integer#MIN_VALUE}
     * @return 返回范围0-59
     */
    public static int getHourOfDay(String date, TimeZone tz) {
        Date _date = parseSmartly(date);
        return _date == null ? Integer.MIN_VALUE : getHourOfDay(_date.getTime(), tz);
    }

    /**
     * 获取指定{@link Date}对应日期是星期几，星期日为7，星期一到六为1-6，使用系统默认的时区
     *
     * @param date 使用{@link #parseSmartly(String)}智能解析时间，如果解析失败返回{@link Integer#MIN_VALUE}
     * @return 返回范围1-7 对应星期一到星期日，可使用{@link #MONDAY}到{@link #SUNDAY}替代
     */
    public static int getDayOfWeek(String date) {
        Date _date = parseSmartly(date);
        return _date == null ? Integer.MIN_VALUE : getDayOfWeek(_date.getTime());
    }

    /**
     * 获取指定{@link Date}对应日期是星期几，星期日为7，星期一到六为1-6，使用指定的时区
     *
     * @param date 使用{@link #parseSmartly(String)}智能解析时间，如果解析失败返回{@link Integer#MIN_VALUE}
     * @return 返回范围1-7 对应星期一到星期日，可使用{@link #MONDAY}到{@link #SUNDAY}替代
     */
    public static int getDayOfWeek(String date, TimeZone tz) {
        Date _date = parseSmartly(date);
        return _date == null ? Integer.MIN_VALUE : getDayOfWeek(_date.getTime(), tz);
    }

    /**
     * 获取指定{@link Date}对应的月份的日期，如2月1日，就返回1，使用系统默认的时区
     *
     * @param date 使用{@link #parseSmartly(String)}智能解析时间，如果解析失败返回{@link Integer#MIN_VALUE}
     * @return 返回范围1-31，最大为当月的最后一天的数值
     */
    public static int getDayOfMonth(String date) {
        Date _date = parseSmartly(date);
        return _date == null ? Integer.MIN_VALUE : getDayOfMonth(_date.getTime());
    }

    /**
     * 获取指定{@link Date}对应的月份的日期，如2月1日，就返回1，使用指定的时区
     *
     * @param date 使用{@link #parseSmartly(String)}智能解析时间，如果解析失败返回{@link Integer#MIN_VALUE}
     * @return 返回范围1-31，最大为当月的最后一天的数值
     */
    public static int getDayOfMonth(String date, TimeZone tz) {
        Date _date = parseSmartly(date);
        return _date == null ? Integer.MIN_VALUE : getDayOfMonth(_date.getTime(), tz);
    }

    /**
     * 获取指定{@link Date}对应日期是这一年的第几天，使用系统默认的时区
     *
     * @param date 使用{@link #parseSmartly(String)}智能解析时间，如果解析失败返回{@link Integer#MIN_VALUE}
     * @return 返回范围平年1-365，闰年1-366
     */
    public static int getDayOfYear(String date) {
        Date _date = parseSmartly(date);
        return _date == null ? Integer.MIN_VALUE : getDayOfYear(_date.getTime());
    }

    /**
     * 获取指定{@link Date}对应日期是这一年的第几天，使用指定的时区
     *
     * @param date 使用{@link #parseSmartly(String)}智能解析时间，如果解析失败返回{@link Integer#MIN_VALUE}
     * @return 返回范围平年1-365，闰年1-366
     */
    public static int getDayOfYear(String date, TimeZone tz) {
        Date _date = parseSmartly(date);
        return _date == null ? Integer.MIN_VALUE : getDayOfYear(_date.getTime(), tz);
    }

    /**
     * 获取指定{@link Date}对应日期的【月】，使用系统默认的时区
     *
     * @param date 使用{@link #parseSmartly(String)}智能解析时间，如果解析失败返回{@link Integer#MIN_VALUE}
     * @return 返回范围1-12，可使用{@link Date}的JANUARY - DECEMBER来指代
     */
    public static int getMonthOfYear(String date) {
        Date _date = parseSmartly(date);
        return _date == null ? Integer.MIN_VALUE : getMonthOfYear(_date.getTime());
    }

    /**
     * 获取指定{@link Date}对应日期的【月】，使用指定的时区
     *
     * @param date 使用{@link #parseSmartly(String)}智能解析时间，如果解析失败返回{@link Integer#MIN_VALUE}
     * @return 返回范围1-12，可使用{@link Date}的JANUARY - DECEMBER来指代
     */
    public static int getMonthOfYear(String date, TimeZone tz) {
        Date _date = parseSmartly(date);
        return _date == null ? Integer.MIN_VALUE : getMonthOfYear(_date.getTime(), tz);
    }

    /**
     * 获取指定{@link Date}对应日期的【年】，公元前的时间，例如221BC将表示为-221，使用系统默认的时区
     *
     * @param date 使用{@link #parseSmartly(String)}智能解析时间，如果解析失败返回{@link Integer#MIN_VALUE}
     */
    public static int getYear(String date) {
        Date _date = parseSmartly(date);
        return _date == null ? Integer.MIN_VALUE : getYear(_date.getTime());
    }

    /**
     * 获取指定{@link Date}对应日期的【年】，公元前的时间，例如221BC将表示为-221，使用指定的时区
     *
     * @param date 使用{@link #parseSmartly(String)}智能解析时间，如果解析失败返回{@link Integer#MIN_VALUE}
     */
    public static int getYear(String date, TimeZone tz) {
        Date _date = parseSmartly(date);
        return _date == null ? Integer.MIN_VALUE : getYear(_date.getTime(), tz);
    }

    /**
     * 修剪获取时刻所在的那一【秒】，例如2015-01-01 12:34:56.789修剪后将返回2015-01-01 12:34.56.000
     */
    public static Date retainSecond(long timestamp) {
        Calendar cal = Calendar.getInstance();
        cal.setLenient(false);
        cal.setTimeInMillis(timestamp);
        cal.set(Calendar.MILLISECOND, 0);
        return cal.getTime();
    }

    /**
     * 修剪获取时刻所在的那一【分】，例如2015-01-01 12:34:56.789修剪后将返回2015-01-01 12:34.00.000
     */
    public static Date retainMinute(long timestamp) {
        Calendar cal = Calendar.getInstance();
        cal.setLenient(false);
        cal.setTimeInMillis(timestamp);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);
        return cal.getTime();
    }

    // 时间修剪

    /**
     * 修剪获取时刻所在的那一【小时】，例如2015-01-01 12:34:56.789修剪后将返回2015-01-01 12:00.00.000，使用系统默认的时区
     */
    public static Date retainHour(long timestamp) {
        return retainHour(timestamp, TimeZone.getDefault());
    }

    /**
     * 修剪获取时刻所在的那一【小时】，例如2015-01-01 12:34:56.789修剪后将返回2015-01-01 12:00.00.000，使用指定的时区
     */
    public static Date retainHour(long timestamp, TimeZone tz) {
        Calendar cal = Calendar.getInstance(tz);
        cal.setLenient(false);
        cal.setTimeInMillis(timestamp);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);
        return cal.getTime();
    }

    /**
     * 修剪获取时刻所在的那一【天】，例如2015-01-01 12:34:56.789修剪后将返回2015-01-01 00:00.00.000，使用系统默认的时区
     */
    public static Date retainDay(long timestamp) {
        return retainDay(timestamp, TimeZone.getDefault());
    }

    /**
     * 修剪获取时刻所在的那一【天】，例如2015-01-01 12:34:56.789修剪后将返回2015-01-01 00:00.00.000，使用指定的时区
     */
    public static Date retainDay(long timestamp, TimeZone tz) {
        Calendar cal = Calendar.getInstance(tz);
        cal.setLenient(false);
        cal.setTimeInMillis(timestamp);
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);
        return cal.getTime();
    }

    /**
     * 修剪获取时刻所在的那一【周的第一天（星期一为每周的第一天）】，例如2015-02-28 12:34:56.789，修剪到星期日将返回2015-02-23 00:00.00.000，使用系统默认的时区
     */
    public static Date retainWeek(long timestamp) {
        return retainWeek(timestamp, TimeZone.getDefault());
    }

    /**
     * 修剪获取时刻所在的那一【周的第一天（星期一为每周的第一天）】，例如2015-02-28 12:34:56.789，修剪到星期日将返回2015-02-23 00:00.00.000，使用指定的时区
     */
    public static Date retainWeek(long timestamp, TimeZone tz) {
        Calendar cal = Calendar.getInstance(tz);
        cal.setLenient(false);
        cal.setFirstDayOfWeek(Calendar.MONDAY);
        cal.setTimeInMillis(timestamp);
        cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);
        return cal.getTime();
    }

    /**
     * 修剪获取时刻所在的那一【月的第一天】，例如2015-01-02 12:34:56.789修剪后将返回2015-01-01 00:00.00.000，使用系统默认的时区
     */
    public static Date retainMonth(long timestamp) {
        return retainMonth(timestamp, TimeZone.getDefault());
    }

    /**
     * 修剪获取时刻所在的那一【月的第一天】，例如2015-01-02 12:34:56.789修剪后将返回2015-01-01 00:00.00.000，使用指定的时区
     */
    public static Date retainMonth(long timestamp, TimeZone tz) {
        Calendar cal = Calendar.getInstance(tz);
        cal.setLenient(false);
        cal.setTimeInMillis(timestamp);
        cal.set(Calendar.DAY_OF_MONTH, 1);
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);
        return cal.getTime();
    }

    /**
     * 修剪获取时刻所在的那一【年的第一天】，例如2015-01-02 12:34:56.789修剪后将返回2015-01-01 00:00.00.000，使用系统默认的时区
     */
    public static Date retainYear(long timestamp) {
        return retainYear(timestamp, TimeZone.getDefault());
    }

    /**
     * 修剪获取时刻所在的那一【年的第一天】，例如2015-01-02 12:34:56.789修剪后将返回2015-01-01 00:00.00.000，使用指定的时区
     */
    public static Date retainYear(long timestamp, TimeZone tz) {
        Calendar cal = Calendar.getInstance(tz);
        cal.setLenient(false);
        cal.setTimeInMillis(timestamp);
        cal.set(Calendar.MONTH, 0);
        cal.set(Calendar.DAY_OF_MONTH, 1);
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);
        return cal.getTime();
    }

    /**
     * 修剪获取时刻所在的那一【秒】，例如2015-01-01 12:34:56.789修剪后将返回2015-01-01 12:34.56.000
     */
    public static Date retainSecond(Date date) {
        return retainSecond(date.getTime());
    }

    /**
     * 修剪获取时刻所在的那一【分】，例如2015-01-01 12:34:56.789修剪后将返回2015-01-01 12:34.00.000
     */
    public static Date retainMinute(Date date) {
        return retainMinute(date.getTime());
    }

    /**
     * 修剪获取时刻所在的那一【小时】，例如2015-01-01 12:34:56.789修剪后将返回2015-01-01 12:00.00.000，使用系统默认的时区
     */
    public static Date retainHour(Date date) {
        return retainHour(date.getTime());
    }

    /**
     * 修剪获取时刻所在的那一【小时】，例如2015-01-01 12:34:56.789修剪后将返回2015-01-01 12:00.00.000，使用指定的时区
     */
    public static Date retainHour(Date date, TimeZone tz) {
        return retainHour(date.getTime(), tz);
    }

    /**
     * 修剪获取时刻所在的那一【天】，例如2015-01-01 12:34:56.789修剪后将返回2015-01-01 00:00.00.000，使用系统默认的时区
     */
    public static Date retainDay(Date date) {
        return retainDay(date.getTime());
    }

    /**
     * 修剪获取时刻所在的那一【天】，例如2015-01-01 12:34:56.789修剪后将返回2015-01-01 00:00.00.000，使用指定的时区
     */
    public static Date retainDay(Date date, TimeZone tz) {
        return retainDay(date.getTime(), tz);
    }

    /**
     * 修剪获取时刻所在的那一【周的第一天（星期一为每周的第一天）】，例如2015-02-28 12:34:56.789，修剪到星期日将返回2015-02-23 00:00.00.000，使用系统默认的时区
     */
    public static Date retainWeek(Date date) {
        return retainWeek(date.getTime());
    }

    /**
     * 修剪获取时刻所在的那一【周的第一天（星期一为每周的第一天）】，例如2015-02-28 12:34:56.789，修剪到星期日将返回2015-02-23 00:00.00.000，使用指定的时区
     */
    public static Date retainWeek(Date date, TimeZone tz) {
        return retainWeek(date.getTime(), tz);
    }

    /**
     * 修剪获取时刻所在的那一【月的第一天】，例如2015-01-02 12:34:56.789修剪后将返回2015-01-01 00:00.00.000，使用系统默认的时区
     */
    public static Date retainMonth(Date date) {
        return retainMonth(date.getTime());
    }

    /**
     * 修剪获取时刻所在的那一【月的第一天】，例如2015-01-02 12:34:56.789修剪后将返回2015-01-01 00:00.00.000，使用指定的时区
     */
    public static Date retainMonth(Date date, TimeZone tz) {
        return retainMonth(date.getTime(), tz);
    }

    /**
     * 修剪获取时刻所在的那一【年的第一天】，例如2015-01-02 12:34:56.789修剪后将返回2015-01-01 00:00.00.000，使用系统默认的时区
     */
    public static Date retainYear(Date date) {
        return retainYear(date.getTime());
    }

    /**
     * 修剪获取时刻所在的那一【年的第一天】，例如2015-01-02 12:34:56.789修剪后将返回2015-01-01 00:00.00.000，使用指定的时区
     */
    public static Date retainYear(Date date, TimeZone tz) {
        return retainYear(date.getTime(), tz);
    }

    /**
     * 修剪获取时刻所在的那一【秒】，例如2015-01-01 12:34:56.789修剪后将返回2015-01-01 12:34.56.000
     *
     * @param date 使用defaultDateTimeFormat解析时间,如果解析失败返回null
     */
    public static Date retainSecond(String date) {
        Date _date = parseSmartly(date);
        return _date == null ? null : retainSecond(_date.getTime());
    }

    /**
     * 修剪获取时刻所在的那一【分】，例如2015-01-01 12:34:56.789修剪后将返回2015-01-01 12:34.00.000
     *
     * @param date 使用defaultDateTimeFormat解析时间,如果解析失败返回null
     */
    public static Date retainMinute(String date) {
        Date _date = parseSmartly(date);
        return _date == null ? null : retainMinute(_date.getTime());
    }

    /**
     * 修剪获取时刻所在的那一【小时】，例如2015-01-01 12:34:56.789修剪后将返回2015-01-01 12:00.00.000，使用系统默认的时区
     *
     * @param date 使用defaultDateTimeFormat解析时间,如果解析失败返回null
     */
    public static Date retainHour(String date) {
        Date _date = parseSmartly(date);
        return _date == null ? null : retainHour(_date.getTime());
    }

    /**
     * 修剪获取时刻所在的那一【小时】，例如2015-01-01 12:34:56.789修剪后将返回2015-01-01 12:00.00.000，使用指定的时区
     *
     * @param date 使用defaultDateTimeFormat解析时间,如果解析失败返回null
     */
    public static Date retainHour(String date, TimeZone tz) {
        Date _date = parseSmartly(date);
        return _date == null ? null : retainHour(_date.getTime(), tz);
    }

    /**
     * 修剪获取时刻所在的那一【天】，例如2015-01-01 12:34:56.789修剪后将返回2015-01-01 00:00.00.000，使用系统默认的时区
     *
     * @param date 使用defaultDateTimeFormat解析时间,如果解析失败返回null
     */
    public static Date retainDay(String date) {
        Date _date = parseSmartly(date);
        return _date == null ? null : retainDay(_date.getTime());
    }

    /**
     * 修剪获取时刻所在的那一【天】，例如2015-01-01 12:34:56.789修剪后将返回2015-01-01 00:00.00.000，使用指定的时区
     *
     * @param date 使用defaultDateTimeFormat解析时间,如果解析失败返回null
     */
    public static Date retainDay(String date, TimeZone tz) {
        Date _date = parseSmartly(date);
        return _date == null ? null : retainDay(_date.getTime(), tz);
    }

    /**
     * 修剪获取时刻所在的那一【周的第一天（星期一为每周的第一天）】，例如2015-02-28 12:34:56.789，修剪到星期日将返回2015-02-23 00:00.00.000，使用系统默认的时区
     *
     * @param date 使用defaultDateTimeFormat解析时间,如果解析失败返回null
     */
    public static Date retainWeek(String date) {
        Date _date = parseSmartly(date);
        return _date == null ? null : retainWeek(_date.getTime());
    }

    /**
     * 修剪获取时刻所在的那一【周的第一天（星期一为每周的第一天）】，例如2015-02-28 12:34:56.789，修剪到星期日将返回2015-02-23 00:00.00.000，使用指定的时区
     *
     * @param date 使用defaultDateTimeFormat解析时间,如果解析失败返回null
     */
    public static Date retainWeek(String date, TimeZone tz) {
        Date _date = parseSmartly(date);
        return _date == null ? null : retainWeek(_date.getTime(), tz);
    }

    /**
     * 修剪获取时刻所在的那一【月的第一天】，例如2015-01-02 12:34:56.789修剪后将返回2015-01-01 00:00.00.000，使用系统默认的时区
     *
     * @param date 使用defaultDateTimeFormat解析时间,如果解析失败返回null
     */
    public static Date retainMonth(String date) {
        Date _date = parseSmartly(date);
        return _date == null ? null : retainMonth(_date.getTime());
    }

    /**
     * 修剪获取时刻所在的那一【月的第一天】，例如2015-01-02 12:34:56.789修剪后将返回2015-01-01 00:00.00.000，使用指定的时区
     *
     * @param date 使用defaultDateTimeFormat解析时间,如果解析失败返回null
     */
    public static Date retainMonth(String date, TimeZone tz) {
        Date _date = parseSmartly(date);
        return _date == null ? null : retainMonth(_date.getTime(), tz);
    }

    /**
     * 修剪获取时刻所在的那一【年的第一天】，例如2015-01-02 12:34:56.789修剪后将返回2015-01-01 00:00.00.000，使用系统默认的时区
     *
     * @param date 使用defaultDateTimeFormat解析时间,如果解析失败返回null
     */
    public static Date retainYear(String date) {
        Date _date = parseSmartly(date);
        return _date == null ? null : retainYear(_date.getTime());
    }

    /**
     * 修剪获取时刻所在的那一【年的第一天】，例如2015-01-02 12:34:56.789修剪后将返回2015-01-01 00:00.00.000，使用指定的时区
     *
     * @param date 使用defaultDateTimeFormat解析时间,如果解析失败返回null
     */
    public static Date retainYear(String date, TimeZone tz) {
        Date _date = parseSmartly(date);
        return _date == null ? null : retainYear(_date.getTime(), tz);
    }

    /**
     * 将毫秒时间戳（自1970-01-01T00:00:00+00:00到所代表时刻的毫秒数）转换为对应格式的时间字符串，默认格式为{@link #defaultDateTimeFormat}，使用当前系统默认的时区
     */
    public static String format(long timestamp) {
        return _getDateFormat(defaultDateTimeFormat, null).format(timestamp);
    }

    /**
     * 将毫秒时间戳（自1970-01-01T00:00:00+00:00到所代表时刻的毫秒数）转换为对应格式的时间字符串，使用指定的格式，使用当前系统默认的时区
     */
    public static String format(long timestamp, String format) {
        return _getDateFormat(format, null).format(timestamp);
    }

    // 格式化输出

    /**
     * 将毫秒时间戳（自1970-01-01T00:00:00+00:00到所代表时刻的毫秒数）转换为对应格式的时间字符串，默认格式为{@link #defaultDateTimeFormat}，使用指定的时区
     */
    public static String format(long timestamp, TimeZone tz) {
        return _getDateFormat(defaultDateTimeFormat, tz).format(timestamp);
    }

    /**
     * 将毫秒时间戳（自1970-01-01T00:00:00+00:00到所代表时刻的毫秒数）转换为对应格式的时间字符串，使用指定的格式和时区
     */
    public static String format(long timestamp, String format, TimeZone tz) {
        return _getDateFormat(format, tz).format(timestamp);
    }

    /**
     * 将毫秒时间戳（自1970-01-01T00:00:00+00:00到所代表时刻的毫秒数）转换为对应格式的时间字符串，默认格式为yyyy-MM-dd，默认输出格式可在{@link DatetimeUtils}中指定，使用当前系统默认的时区
     */
    public static String formatDate(long timestamp) {
        return _getDateFormat(defaultDateFormat, null).format(timestamp);
    }

    /**
     * 将毫秒时间戳（自1970-01-01T00:00:00+00:00到所代表时刻的毫秒数）转换为对应格式的时间字符串，默认格式为yyyy-MM-dd，默认输出格式可在{@link DatetimeUtils}中指定，使用指定的时区
     */
    public static String formatDate(long timestamp, TimeZone tz) {
        return _getDateFormat(defaultDateFormat, tz).format(timestamp);
    }

    /**
     * 将{@link Date}转换为对应格式的时间字符串，默认格式为{@link #defaultDateTimeFormat}，使用当前系统默认的时区
     */
    public static String format(Date date) {
        return _getDateFormat(defaultDateTimeFormat, null).format(date);
    }

    /**
     * 将{@link Date}转换为对应格式的时间字符串，使用指定的格式，使用当前系统默认的时区
     */
    public static String format(Date date, String format) {
        return _getDateFormat(format, null).format(date);
    }

    /**
     * 将{@link Date}转换为对应格式的时间字符串，默认格式为{@link #defaultDateTimeFormat}，使用指定的时区
     */
    public static String format(Date date, TimeZone tz) {
        return _getDateFormat(defaultDateTimeFormat, tz).format(date);
    }

    /**
     * 将{@link Date}转换为对应格式的时间字符串，使用指定的格式和时区
     */
    public static String format(Date date, String format, TimeZone tz) {
        return _getDateFormat(format, tz).format(date);
    }

    /**
     * 将{@link Date}转换为对应格式的时间字符串，默认格式为yyyy-MM-dd，默认输出格式可在{@link DatetimeUtils}中指定，使用当前系统默认的时区
     */
    public static String formatDate(Date date) {
        return _getDateFormat(defaultDateFormat, null).format(date);
    }

    /**
     * 将{@link Date}转换为对应格式的时间字符串，默认格式为yyyy-MM-dd，默认输出格式可在{@link DatetimeUtils}中指定，使用指定的时区
     */
    public static String formatDate(Date date, TimeZone tz) {
        return _getDateFormat(defaultDateFormat, tz).format(date);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻加上指定的【毫秒】数后的结果
     *
     * @param millis 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date plusMillis(long timestamp, long millis) {
        return new Date(timestamp + millis);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻加上指定的【秒】数后的结果
     *
     * @param seconds 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date plusSeconds(long timestamp, long seconds) {
        return new Date(timestamp + MILLIS_PER_SECOND * seconds);
    }

    // 时间操作

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻加上指定的【分】数后的结果
     *
     * @param minutes 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date plusMinutes(long timestamp, long minutes) {
        return new Date(timestamp + MILLIS_PER_MINUTE * minutes);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻加上指定的【时】数后的结果
     *
     * @param hours 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date plusHours(long timestamp, long hours) {
        return new Date(timestamp + MILLIS_PER_HOUR * hours);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻加上指定的【天】数后的结果
     *
     * @param days 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date plusDays(long timestamp, long days) {
        return new Date(timestamp + MILLIS_PER_DAY * days);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻加上指定的【周】数后的结果
     *
     * @param weeks 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date plusWeeks(long timestamp, long weeks) {
        return new Date(timestamp + MILLIS_PER_WEEK * weeks);
    }

    /**
     * <pre>
     * 返回一个新生成的{@link Date}对象，为当前时刻加上指定的【月】数后的结果，使用当前系统默认的时区
     *
     * 默认情况是对应月份的这一天，如果这一天不存在，返回的将是这个月的最后一天
     * 如今天是3月31日，加1个月，将返回4月30日
     *
     * @param months 如果传入0，并且是{@link Date}将直接返回原对象
     * </pre>
     */
    public static Date plusMonths(long timestamp, int months) {
        return plusMonths(timestamp, months, TimeZone.getDefault());
    }

    /**
     * <pre>
     * 返回一个新生成的{@link Date}对象，为当前时刻加上指定的【月】数后的结果，使用指定的时区
     *
     * 默认情况是对应月份的这一天，如果这一天不存在，返回的将是这个月的最后一天
     * 如今天是3月31日，加1个月，将返回4月30日
     *
     * @param months 如果传入0，并且是{@link Date}将直接返回原对象
     * </pre>
     */
    public static Date plusMonths(long timestamp, int months, TimeZone tz) {
        if (months == 0) {
            return new Date(timestamp);
        }
        Calendar cal = Calendar.getInstance(tz);
        cal.setLenient(false);
        cal.setTimeInMillis(timestamp);
        cal.add(Calendar.MONTH, months);
        return cal.getTime();
    }

    /**
     * <pre>
     * 返回一个新生成的{@link Date}对象，为当前时刻加上指定的【年】数后的结果，使用当前系统默认的时区
     *
     * 如果当年是闰年2月29日，加上一年后是平年，返回的将是2月28日
     *
     * @param years 如果传入0，并且是{@link Date}将直接返回原对象
     * </pre>
     */
    public static Date plusYears(long timestamp, int years) {
        return plusYears(timestamp, years, TimeZone.getDefault());
    }

    /**
     * <pre>
     * 返回一个新生成的{@link Date}对象，为当前时刻加上指定的【年】数后的结果，使用指定的时区
     *
     * 如果当年是闰年2月29日，加上一年后是平年，返回的将是2月28日
     *
     * @param years 如果传入0，并且是{@link Date}将直接返回原对象
     * </pre>
     */
    public static Date plusYears(long timestamp, int years, TimeZone tz) {
        if (years == 0) {
            return new Date(timestamp);
        }
        Calendar cal = Calendar.getInstance(tz);
        cal.setLenient(false);
        cal.setTimeInMillis(timestamp);
        cal.add(Calendar.YEAR, years);
        return cal.getTime();
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻减去指定的【毫秒】数后的结果
     *
     * @param millis 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date minusMillis(long timestamp, long millis) {
        return plusMillis(timestamp, -millis);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻减去指定的【秒】数后的结果
     *
     * @param seconds 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date minusSeconds(long timestamp, long seconds) {
        return plusSeconds(timestamp, -seconds);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻减去指定的【分】数后的结果
     *
     * @param minutes 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date minusMinutes(long timestamp, long minutes) {
        return plusMinutes(timestamp, -minutes);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻减去指定的【时】数后的结果
     *
     * @param hours 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date minusHours(long timestamp, long hours) {
        return plusHours(timestamp, -hours);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻减去指定的【天】数后的结果
     *
     * @param days 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date minusDays(long timestamp, long days) {
        return plusDays(timestamp, -days);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻减去指定的【周】数后的结果
     *
     * @param weeks 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date minusWeeks(long timestamp, long weeks) {
        return plusWeeks(timestamp, -weeks);
    }

    /**
     * <pre>
     * 返回一个新生成的{@link Date}对象，为当前时刻减去指定的【月】数后的结果，使用当前系统默认的时区
     *
     * 默认情况是对应月份的这一天，如果这一天不存在，返回的将是这个月的最后一天
     * 如今天是5月31日，减1个月，将返回4月30日
     *
     * @param months 如果传入0，并且是{@link Date}将直接返回原对象
     * </pre>
     */
    public static Date minusMonths(long timestamp, int months) {
        return plusMonths(timestamp, -months);
    }

    /**
     * <pre>
     * 返回一个新生成的{@link Date}对象，为当前时刻减去指定的【月】数后的结果，使用指定的时区
     *
     * 默认情况是对应月份的这一天，如果这一天不存在，返回的将是这个月的最后一天
     * 如今天是5月31日，减1个月，将返回4月30日
     *
     * @param months 如果传入0，并且是{@link Date}将直接返回原对象
     * </pre>
     */
    public static Date minusMonths(long timestamp, int months, TimeZone tz) {
        return plusMonths(timestamp, -months, tz);
    }

    /**
     * <pre>
     * 返回一个新生成的{@link Date}对象，为当前时刻减去指定的【年】数后的结果，使用当前系统默认的时区
     *
     * 如果当年是闰年2月29日，减去一年后是平年，返回的将是2月28日
     *
     * @param years 如果传入0，并且是{@link Date}将直接返回原对象
     * </pre>
     */
    public static Date minusYears(long timestamp, int years) {
        return plusYears(timestamp, -years);
    }

    /**
     * <pre>
     * 返回一个新生成的{@link Date}对象，为当前时刻减去指定的【年】数后的结果，使用指定的时区
     *
     * 如果当年是闰年2月29日，减去一年后是平年，返回的将是2月28日
     *
     * @param years 如果传入0，并且是{@link Date}将直接返回原对象
     * </pre>
     */
    public static Date minusYears(long timestamp, int years, TimeZone tz) {
        return plusYears(timestamp, -years, tz);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻加上指定的【毫秒】数后的结果
     *
     * @param millis 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date plusMillis(Date date, long millis) {
        if (millis == 0 && date instanceof Date) {
            return (Date) date;
        }
        return new Date(date.getTime() + millis);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻加上指定的【秒】数后的结果
     *
     * @param seconds 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date plusSeconds(Date date, long seconds) {
        if (seconds == 0 && date instanceof Date) {
            return (Date) date;
        }
        return new Date(date.getTime() + MILLIS_PER_SECOND * seconds);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻加上指定的【分】数后的结果
     *
     * @param minutes 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date plusMinutes(Date date, long minutes) {
        if (minutes == 0 && date instanceof Date) {
            return (Date) date;
        }
        return new Date(date.getTime() + MILLIS_PER_MINUTE * minutes);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻加上指定的【时】数后的结果
     *
     * @param hours 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date plusHours(Date date, long hours) {
        if (hours == 0 && date instanceof Date) {
            return (Date) date;
        }
        return new Date(date.getTime() + MILLIS_PER_HOUR * hours);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻加上指定的【天】数后的结果
     *
     * @param days 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date plusDays(Date date, long days) {
        if (days == 0 && date instanceof Date) {
            return (Date) date;
        }
        return new Date(date.getTime() + MILLIS_PER_DAY * days);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻加上指定的【周】数后的结果
     *
     * @param weeks 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date plusWeeks(Date date, long weeks) {
        if (weeks == 0 && date instanceof Date) {
            return (Date) date;
        }
        return new Date(date.getTime() + MILLIS_PER_WEEK * weeks);
    }

    /**
     * <pre>
     * 返回一个新生成的{@link Date}对象，为当前时刻加上指定的【月】数后的结果，使用当前系统默认的时区
     *
     * 默认情况是对应月份的这一天，如果这一天不存在，返回的将是这个月的最后一天
     * 如今天是3月31日，加1个月，将返回4月30日
     *
     * @param months 如果传入0，并且是{@link Date}将直接返回原对象
     * </pre>
     */
    public static Date plusMonths(Date date, int months) {
        return plusMonths(date, months, TimeZone.getDefault());
    }

    /**
     * <pre>
     * 返回一个新生成的{@link Date}对象，为当前时刻加上指定的【月】数后的结果，使用指定的时区
     *
     * 默认情况是对应月份的这一天，如果这一天不存在，返回的将是这个月的最后一天
     * 如今天是3月31日，加1个月，将返回4月30日
     *
     * @param months 如果传入0，并且是{@link Date}将直接返回原对象
     * </pre>
     */
    public static Date plusMonths(Date date, int months, TimeZone tz) {
        if (months == 0) {
            return date;
        }
        Calendar cal = Calendar.getInstance(tz);
        cal.setLenient(false);
        cal.setTime(date);
        cal.add(Calendar.MONTH, months);
        return cal.getTime();
    }

    /**
     * <pre>
     * 返回一个新生成的{@link Date}对象，为当前时刻加上指定的【年】数后的结果，使用当前系统默认的时区
     *
     * 如果当年是闰年2月29日，加上一年后是平年，返回的将是2月28日
     *
     * @param years 如果传入0，并且是{@link Date}将直接返回原对象
     * </pre>
     */
    public static Date plusYears(Date date, int years) {
        return plusYears(date, years, TimeZone.getDefault());
    }

    /**
     * <pre>
     * 返回一个新生成的{@link Date}对象，为当前时刻加上指定的【年】数后的结果，使用指定的时区
     *
     * 如果当年是闰年2月29日，加上一年后是平年，返回的将是2月28日
     *
     * @param years 如果传入0，并且是{@link Date}将直接返回原对象
     * </pre>
     */
    public static Date plusYears(Date date, int years, TimeZone tz) {
        if (years == 0) {
            return date;
        }
        Calendar cal = Calendar.getInstance(tz);
        cal.setLenient(false);
        cal.setTime(date);
        cal.add(Calendar.YEAR, years);
        return cal.getTime();
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻减去指定的【毫秒】数后的结果
     *
     * @param millis 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date minusMillis(Date date, long millis) {
        return plusMillis(date, -millis);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻减去指定的【秒】数后的结果
     *
     * @param seconds 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date minusSeconds(Date date, long seconds) {
        return plusSeconds(date, -seconds);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻减去指定的【分】数后的结果
     *
     * @param minutes 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date minusMinutes(Date date, long minutes) {
        return plusMinutes(date, -minutes);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻减去指定的【时】数后的结果
     *
     * @param hours 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date minusHours(Date date, long hours) {
        return plusHours(date, -hours);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻减去指定的【天】数后的结果
     *
     * @param days 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date minusDays(Date date, long days) {
        return plusDays(date, -days);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻减去指定的【周】数后的结果
     *
     * @param weeks 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date minusWeeks(Date date, long weeks) {
        return plusWeeks(date, -weeks);
    }

    /**
     * <pre>
     * 返回一个新生成的{@link Date}对象，为当前时刻减去指定的【月】数后的结果，使用当前系统默认的时区
     *
     * 默认情况是对应月份的这一天，如果这一天不存在，返回的将是这个月的最后一天
     * 如今天是5月31日，减1个月，将返回4月30日
     * </pre>
     *
     * @param months 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date minusMonths(Date date, int months) {
        return plusMonths(date, -months);
    }

    /**
     * <pre>
     * 返回一个新生成的{@link Date}对象，为当前时刻减去指定的【月】数后的结果，使用指定的时区
     *
     * 默认情况是对应月份的这一天，如果这一天不存在，返回的将是这个月的最后一天
     * 如今天是5月31日，减1个月，将返回4月30日
     * </pre>
     *
     * @param months 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date minusMonths(Date date, int months, TimeZone tz) {
        return plusMonths(date, -months, tz);
    }

    /**
     * <pre>
     * 返回一个新生成的{@link Date}对象，为当前时刻减去指定的【年】数后的结果，使用当前系统默认的时区
     *
     * 如果当年是闰年2月29日，减去一年后是平年，返回的将是2月28日
     * </pre>
     *
     * @param years 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date minusYears(Date date, int years) {
        return plusYears(date, -years);
    }

    /**
     * <pre>
     * 返回一个新生成的{@link Date}对象，为当前时刻减去指定的【年】数后的结果，使用指定的时区
     *
     * 如果当年是闰年2月29日，减去一年后是平年，返回的将是2月28日
     * </pre>
     *
     * @param years 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date minusYears(Date date, int years, TimeZone tz) {
        return plusYears(date, -years, tz);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻加上指定的【毫秒】数后的结果
     *
     * @param date   使用defaultDateTimeFormat解析时间,如果解析失败返回null
     * @param millis 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date plusMillis(String date, long millis) {
        if (millis == 0) {
            return parseSmartly(date);
        }
        Date _date = parseSmartly(date);
        return _date == null ? null : new Date(_date.getTime() + millis);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻加上指定的【秒】数后的结果
     *
     * @param date    使用defaultDateTimeFormat解析时间,如果解析失败返回null
     * @param seconds 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date plusSeconds(String date, long seconds) {
        if (seconds == 0) {
            return parseSmartly(date);
        }
        Date _date = parseSmartly(date);
        return _date == null ? null : new Date(_date.getTime() + MILLIS_PER_SECOND * seconds);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻加上指定的【分】数后的结果
     *
     * @param date    使用defaultDateTimeFormat解析时间,如果解析失败返回null
     * @param minutes 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date plusMinutes(String date, long minutes) {
        if (minutes == 0) {
            return parseSmartly(date);
        }
        Date _date = parseSmartly(date);
        return _date == null ? null : new Date(_date.getTime() + MILLIS_PER_MINUTE * minutes);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻加上指定的【时】数后的结果
     *
     * @param date  使用defaultDateTimeFormat解析时间,如果解析失败返回null
     * @param hours 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date plusHours(String date, long hours) {
        if (hours == 0) {
            return parseSmartly(date);
        }
        Date _date = parseSmartly(date);
        return _date == null ? null : new Date(parseSmartly(date).getTime() + MILLIS_PER_HOUR * hours);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻加上指定的【天】数后的结果
     *
     * @param date 使用defaultDateTimeFormat解析时间,如果解析失败返回null
     * @param days 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date plusDays(String date, long days) {
        if (days == 0) {
            return parseSmartly(date);
        }
        Date _date = parseSmartly(date);
        return _date == null ? null : new Date(_date.getTime() + MILLIS_PER_DAY * days);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻加上指定的【周】数后的结果
     *
     * @param date  使用defaultDateTimeFormat解析时间,如果解析失败返回null
     * @param weeks 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date plusWeeks(String date, long weeks) {
        if (weeks == 0) {
            return parseSmartly(date);
        }
        Date _date = parseSmartly(date);
        return _date == null ? null : new Date(_date.getTime() + MILLIS_PER_WEEK * weeks);
    }

    /**
     * <pre>
     * 返回一个新生成的{@link Date}对象，为当前时刻加上指定的【月】数后的结果，使用当前系统默认的时区
     *
     * 默认情况是对应月份的这一天，如果这一天不存在，返回的将是这个月的最后一天
     * 如今天是3月31日，加1个月，将返回4月30日
     * </pre>
     *
     * @param date   使用defaultDateTimeFormat解析时间,如果解析失败返回null
     * @param months 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date plusMonths(String date, int months) {
        return plusMonths(date, months, TimeZone.getDefault());
    }

    /**
     * <pre>
     * 返回一个新生成的{@link Date}对象，为当前时刻加上指定的【月】数后的结果，使用指定的时区
     *
     * 默认情况是对应月份的这一天，如果这一天不存在，返回的将是这个月的最后一天
     * 如今天是3月31日，加1个月，将返回4月30日
     * </pre>
     *
     * @param date   使用defaultDateTimeFormat解析时间,如果解析失败返回null
     * @param months 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date plusMonths(String date, int months, TimeZone tz) {
        if (months == 0) {
            return parseSmartly(date);
        }
        Date _date = parseSmartly(date);
        if (_date == null) {
            return null;
        }

        Calendar cal = Calendar.getInstance(tz);
        cal.setLenient(false);
        cal.setTime(_date);
        cal.add(Calendar.MONTH, months);
        return cal.getTime();
    }

    /**
     * <pre>
     * 返回一个新生成的{@link Date}对象，为当前时刻加上指定的【年】数后的结果，使用当前系统默认的时区
     *
     * 如果当年是闰年2月29日，加上一年后是平年，返回的将是2月28日
     * </pre>
     *
     * @param date  使用defaultDateTimeFormat解析时间,如果解析失败返回null
     * @param years 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date plusYears(String date, int years) {
        return plusYears(date, years, TimeZone.getDefault());
    }

    /**
     * <pre>
     * 返回一个新生成的{@link Date}对象，为当前时刻加上指定的【年】数后的结果，使用指定的时区
     *
     * 如果当年是闰年2月29日，加上一年后是平年，返回的将是2月28日
     * </pre>
     *
     * @param date  使用defaultDateTimeFormat解析时间,如果解析失败返回null
     * @param years 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date plusYears(String date, int years, TimeZone tz) {
        if (years == 0) {
            return parseSmartly(date);
        }
        Date _date = parseSmartly(date);
        if (_date == null) {
            return null;
        }
        Calendar cal = Calendar.getInstance(tz);
        cal.setLenient(false);
        cal.setTime(_date);
        cal.add(Calendar.YEAR, years);
        return cal.getTime();
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻减去指定的【毫秒】数后的结果
     *
     * @param date   使用defaultDateTimeFormat解析时间,如果解析失败返回null
     * @param millis 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date minusMillis(String date, long millis) {
        Date _date = parseSmartly(date);
        return _date == null ? null : plusMillis(_date, -millis);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻减去指定的【秒】数后的结果
     *
     * @param date    使用defaultDateTimeFormat解析时间,如果解析失败返回null
     * @param seconds 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date minusSeconds(String date, long seconds) {
        Date _date = parseSmartly(date);
        return _date == null ? null : plusSeconds(_date, -seconds);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻减去指定的【分】数后的结果
     *
     * @param date    使用defaultDateTimeFormat解析时间,如果解析失败返回null
     * @param minutes 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date minusMinutes(String date, long minutes) {
        Date _date = parseSmartly(date);
        return _date == null ? null : plusMinutes(_date, -minutes);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻减去指定的【时】数后的结果
     *
     * @param date  使用defaultDateTimeFormat解析时间,如果解析失败返回null
     * @param hours 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date minusHours(String date, long hours) {
        Date _date = parseSmartly(date);
        return _date == null ? null : plusHours(_date, -hours);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻减去指定的【天】数后的结果
     *
     * @param date 使用defaultDateTimeFormat解析时间,如果解析失败返回null
     * @param days 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date minusDays(String date, long days) {
        Date _date = parseSmartly(date);
        return _date == null ? null : plusDays(_date, -days);
    }

    /**
     * 返回一个新生成的{@link Date}对象，为当前时刻减去指定的【周】数后的结果
     *
     * @param date  使用defaultDateTimeFormat解析时间,如果解析失败返回null
     * @param weeks 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date minusWeeks(String date, long weeks) {
        Date _date = parseSmartly(date);
        return _date == null ? null : plusWeeks(_date, -weeks);
    }

    /**
     * <pre>
     * 返回一个新生成的{@link Date}对象，为当前时刻减去指定的【月】数后的结果，使用当前系统默认的时区
     *
     * 默认情况是对应月份的这一天，如果这一天不存在，返回的将是这个月的最后一天
     * 如今天是5月31日，减1个月，将返回4月30日
     * </pre>
     *
     * @param date   使用defaultDateTimeFormat解析时间,如果解析失败返回null
     * @param months 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date minusMonths(String date, int months) {
        Date _date = parseSmartly(date);
        return _date == null ? null : plusMonths(_date, -months);
    }

    /**
     * <pre>
     * 返回一个新生成的{@link Date}对象，为当前时刻减去指定的【月】数后的结果，使用指定的时区
     *
     * 默认情况是对应月份的这一天，如果这一天不存在，返回的将是这个月的最后一天
     * 如今天是5月31日，减1个月，将返回4月30日
     * </pre>
     *
     * @param date   使用defaultDateTimeFormat解析时间,如果解析失败返回null
     * @param months 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date minusMonths(String date, int months, TimeZone tz) {
        Date _date = parseSmartly(date);
        return _date == null ? null : plusMonths(_date, -months, tz);
    }

    /**
     * <pre>
     * 返回一个新生成的{@link Date}对象，为当前时刻减去指定的【年】数后的结果，使用当前系统默认的时区
     *
     * 如果当年是闰年2月29日，减去一年后是平年，返回的将是2月28日
     * </pre>
     *
     * @param date  使用defaultDateTimeFormat解析时间,如果解析失败返回null
     * @param years 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date minusYears(String date, int years) {
        Date _date = parseSmartly(date);
        return _date == null ? null : plusYears(_date, -years);
    }

    /**
     * <pre>
     * 返回一个新生成的{@link Date}对象，为当前时刻减去指定的【年】数后的结果，使用指定的时区
     *
     * 如果当年是闰年2月29日，减去一年后是平年，返回的将是2月28日
     * </pre>
     *
     * @param date  使用defaultDateTimeFormat解析时间,如果解析失败返回null
     * @param years 如果传入0，并且是{@link Date}将直接返回原对象
     */
    public static Date minusYears(String date, int years, TimeZone tz) {
        Date _date = parseSmartly(date);
        return _date == null ? null : plusYears(_date, -years, tz);
    }

    /**
     * 获取两时刻相减的【毫秒】数差值 end-start
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static long getMillisBetween(long start, long end) {
        return end - start;
    }

    /**
     * 获取两时刻相减的【秒】数差值 end-start，将直接比较二者的时间差，>=1000ms才算是1s
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static long getSecondsBetween(long start, long end) {
        return (end - start) / MILLIS_PER_SECOND;
    }

    // 时间差

    /**
     * 获取两时刻相减的【分】数差值 end-start，将直接比较二者的时间差，>=60s才算是1min
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static long getMinutesBetween(long start, long end) {
        return (end - start) / MILLIS_PER_MINUTE;
    }

    /**
     * 获取两时刻相减的【小时】数差值 end-start，将直接比较二者的时间差，>=60min才算是1h
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static long getHoursBetween(long start, long end) {
        return (end - start) / MILLIS_PER_HOUR;
    }

    /**
     * 获取两时刻相减的【天】数差值 end-start，将直接比较二者的时间差，>=24h才算是1day
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static long getDaysBetween(long start, long end) {
        return (end - start) / MILLIS_PER_DAY;
    }

    /**
     * 获取两时刻相减的【天】数差值 end-start，将比较二者的日期差，以自然天计算，日期相差1但差距不满24h的也可以算是1day，使用系统默认的时区
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static long getDaysInCalendarBetween(long start, long end) {
        return getDaysInCalendarBetween(start, end, TimeZone.getDefault());
    }

    /**
     * 获取两时刻相减的【天】数差值 end-start，将比较二者的日期差，以自然天计算，日期相差1但差距不满24h的也可以算是1day，使用指定的时区
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static long getDaysInCalendarBetween(long start, long end, TimeZone tz) {
        long s = retainDay(start, tz).getTime();
        long e = retainDay(end, tz).getTime();
        return (e - s) / MILLIS_PER_DAY;
    }

    /**
     * 获取两时刻相减的【周】数差值 end-start，必须要满足7*24h才能算是一周
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static long getWeeksBetween(long start, long end) {
        return (end - start) / MILLIS_PER_WEEK;
    }

    /**
     * 获取两时刻相减的【周】数差值 end-start，将比较二者的日期差，以自然天7天计算，日期相差1但差距不满24h的也可以算是1day，使用系统默认的时区
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static long getWeeksInCalendarBetween(long start, long end) {
        return getWeeksInCalendarBetween(start, end, TimeZone.getDefault());
    }

    /**
     * 获取两时刻相减的【周】数差值 end-start，将比较二者的日期差，以自然天7天计算，日期相差1但差距不满24h的也可以算是1day，使用指定的时区
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static long getWeeksInCalendarBetween(long start, long end, TimeZone tz) {
        long s = retainDay(start, tz).getTime();
        long e = retainDay(end, tz).getTime();
        return (e - s) / MILLIS_PER_WEEK;
    }

    /**
     * <pre>
     * 获取两时刻相减的【月】数差值 end-start，要求end的日期必须要>=start的才能算是一个月（一个月天数可不固定，分大小月），要求【必须满24h才能算1day】，使用系统默认的时区
     * 例如start为2015-01-01 12:00:00则end必须要>=2015-02-01 12:00:00才能算是一个月
     * </pre>
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static int getMonthsBetween(long start, long end) {
        return getMonthsBetween(start, end, TimeZone.getDefault());
    }

    /**
     * <pre>
     * 获取两时刻相减的【月】数差值 end-start，要求end的日期必须要>=start的才能算是一个月（一个月天数可不固定，分大小月），要求【必须满24h才能算1day】，使用指定的时区
     * 例如start为2015-01-01 12:00:00则end必须要>=2015-02-01 12:00:00才能算是一个月
     * </pre>
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static int getMonthsBetween(long start, long end, TimeZone tz) {
        int s = getYear(start, tz) * 12 + getMonthOfYear(start, tz);
        int e = getYear(end) * 12 + getMonthOfYear(end, tz);
        if (e == s) {
            return 0;
        } else if (e > s) {
            if (plusMonths(start, e - s, tz).getTime() > end) { // 说明零头不满一个月，不算一个月，要在结果中要减去
                return e - s - 1;
            }
            return e - s;
        }
        if (plusMonths(end, s - e, tz).getTime() > start) { // 说明零头不满一个月，不算一个月，要在结果中要减去
            return s - e - 1;
        }
        return e - s;
    }

    /**
     * <pre>
     * 获取两时刻相减的【月】数差值 end-start，要求end的日期必须要>=start的才能算是一个月（一个月天数可不固定，分大小月），不要求一定要满24h，使用系统默认的时区
     * 例如start为2015-01-01 12:00:00则end只需>=2015-02-01 00:00:00就可以算是一个月
     * </pre>
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static int getMonthsInCalendarBetween(long start, long end) {
        return getMonthsInCalendarBetween(start, end, TimeZone.getDefault());
    }

    /**
     * <pre>
     * 获取两时刻相减的【月】数差值 end-start，要求end的日期必须要>=start的才能算是一个月（一个月天数可不固定，分大小月），不要求一定要满24h，使用指定的时区
     * 例如start为2015-01-01 12:00:00则end只需>=2015-02-01 00:00:00就可以算是一个月
     * </pre>
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static int getMonthsInCalendarBetween(long start, long end, TimeZone tz) {
        int s = getYear(start, tz) * 12 + getMonthOfYear(start, tz);
        int e = getYear(end, tz) * 12 + getMonthOfYear(end, tz);
        if (e == s) {
            return 0;
        } else if (e > s) {
            // 由于是InCalendar不精确到时分秒毫秒，直接算到天就行了
            if (retainDay(plusMonths(start, e - s, tz), tz).getTime() > end) { // 说明零头不满一个月，不算一个月，要在结果中要减去
                return e - s - 1;
            }
            return e - s;
        }
        if (retainDay(plusMonths(end, s - e, tz), tz).getTime() > start) { // 说明零头不满一个月，不算一个月，要在结果中要减去
            return s - e - 1;
        }
        return e - s;
    }

    /**
     * <pre>
     * 获取两时刻相减的【年】数差值 end-start，要求end的日期必须要>=start的才能算是一年（支持平年/闰年的处理），要求【必须满24h才能算1day】，使用系统默认的时区
     * 例如start为2015-01-01 12:00:00则end必须要>=2016-01-01 12:00:00才能算是一年
     * </pre>
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static int getYearsBetween(long start, long end) {
        return getMonthsBetween(start, end) / 12;
    }

    /**
     * <pre>
     * 获取两时刻相减的【年】数差值 end-start，要求end的日期必须要>=start的才能算是一年（支持平年/闰年的处理），要求【必须满24h才能算1day】，使用指定的时区
     * 例如start为2015-01-01 12:00:00则end必须要>=2016-01-01 12:00:00才能算是一年
     * </pre>
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static int getYearsBetween(long start, long end, TimeZone tz) {
        return getMonthsBetween(start, end, tz) / 12;
    }

    /**
     * <pre>
     * 获取两时刻相减的【年】数差值 end-start，要求end的日期必须要>=start的才能算是一年（支持平年/闰年的处理），不要求一定要满24h，使用系统默认的时区
     * 例如start为2015-01-01 12:00:00则end只需>=2016-01-01 00:00:00就可以算是一年
     * </pre>
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static int getYearsInCalendarBetween(long start, long end) {
        return getMonthsInCalendarBetween(start, end) / 12;
    }

    /**
     * <pre>
     * 获取两时刻相减的【年】数差值 end-start，要求end的日期必须要>=start的才能算是一年（支持平年/闰年的处理），不要求一定要满24h，使用指定的时区
     * 例如start为2015-01-01 12:00:00则end只需>=2016-01-01 00:00:00就可以算是一年
     * </pre>
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static int getYearsInCalendarBetween(long start, long end, TimeZone tz) {
        return getMonthsInCalendarBetween(start, end, tz) / 12;
    }

    /**
     * 获取两时刻相减的【毫秒】数差值 end-start
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static long getMillisBetween(Date start, Date end) {
        return getMillisBetween(start.getTime(), end.getTime());
    }

    /**
     * 获取两时刻相减的【秒】数差值 end-start，将直接比较二者的时间差，>=1000ms才算是1s
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static long getSecondsBetween(Date start, Date end) {
        return getSecondsBetween(start.getTime(), end.getTime());
    }

    /**
     * 获取两时刻相减的【分】数差值 end-start，将直接比较二者的时间差，>=60s才算是1min
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static long getMinutesBetween(Date start, Date end) {
        return getMinutesBetween(start.getTime(), end.getTime());
    }

    /**
     * 获取两时刻相减的【小时】数差值 end-start，将直接比较二者的时间差，>=60min才算是1h
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static long getHoursBetween(Date start, Date end) {
        return getHoursBetween(start.getTime(), end.getTime());
    }

    /**
     * 获取两时刻相减的【天】数差值 end-start，将直接比较二者的时间差，>=24h才算是1day
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static long getDaysBetween(Date start, Date end) {
        return getDaysBetween(start.getTime(), end.getTime());
    }

    /**
     * 获取两时刻相减的【天】数差值 end-start，将比较二者的日期差，以自然天计算，日期相差1但差距不满24h的也可以算是1day，使用系统默认的时区
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static long getDaysInCalendarBetween(Date start, Date end) {
        return getDaysInCalendarBetween(start.getTime(), end.getTime());
    }

    /**
     * 获取两时刻相减的【天】数差值 end-start，将比较二者的日期差，以自然天计算，日期相差1但差距不满24h的也可以算是1day，使用指定的时区
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static long getDaysInCalendarBetween(Date start, Date end, TimeZone tz) {
        return getDaysInCalendarBetween(start.getTime(), end.getTime(), tz);
    }

    /**
     * 获取两时刻相减的【周】数差值 end-start，必须要满足7*24h才能算是一周
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static long getWeeksBetween(Date start, Date end) {
        return getWeeksBetween(start.getTime(), end.getTime());
    }

    /**
     * 获取两时刻相减的【周】数差值 end-start，将比较二者的日期差，以自然天7天计算，日期相差1但差距不满24h的也可以算是1day，使用系统默认的时区
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static long getWeeksInCalendarBetween(Date start, Date end) {
        return getWeeksInCalendarBetween(start.getTime(), end.getTime());
    }

    /**
     * 获取两时刻相减的【周】数差值 end-start，将比较二者的日期差，以自然天7天计算，日期相差1但差距不满24h的也可以算是1day，使用指定的时区
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static long getWeeksInCalendarBetween(Date start, Date end, TimeZone tz) {
        return getWeeksInCalendarBetween(start.getTime(), end.getTime(), tz);
    }

    /**
     * <pre>
     * 获取两时刻相减的【月】数差值 end-start，要求end的日期必须要>=start的才能算是一个月（一个月天数可不固定，分大小月），要求【必须满24h才能算1day】，使用系统默认的时区
     * 例如start为2015-01-01 12:00:00则end必须要>=2015-02-01 12:00:00才能算是一个月
     * </pre>
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static int getMonthsBetween(Date start, Date end) {
        return getMonthsBetween(start.getTime(), end.getTime());
    }

    /**
     * <pre>
     * 获取两时刻相减的【月】数差值 end-start，要求end的日期必须要>=start的才能算是一个月（一个月天数可不固定，分大小月），要求【必须满24h才能算1day】，使用指定的时区
     * 例如start为2015-01-01 12:00:00则end必须要>=2015-02-01 12:00:00才能算是一个月
     * </pre>
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static int getMonthsBetween(Date start, Date end, TimeZone tz) {
        return getMonthsBetween(start.getTime(), end.getTime(), tz);
    }

    /**
     * <pre>
     * 获取两时刻相减的【月】数差值 end-start，要求end的日期必须要>=start的才能算是一个月（一个月天数可不固定，分大小月），不要求一定要满24h，使用系统默认的时区
     * 例如start为2015-01-01 12:00:00则end只需>=2015-02-01 00:00:00就可以算是一个月
     * </pre>
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static int getMonthsInCalendarBetween(Date start, Date end) {
        return getMonthsInCalendarBetween(start.getTime(), end.getTime());
    }

    /**
     * <pre>
     * 获取两时刻相减的【月】数差值 end-start，要求end的日期必须要>=start的才能算是一个月（一个月天数可不固定，分大小月），不要求一定要满24h，使用指定的时区
     * 例如start为2015-01-01 12:00:00则end只需>=2015-02-01 00:00:00就可以算是一个月
     * </pre>
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static int getMonthsInCalendarBetween(Date start, Date end, TimeZone tz) {
        return getMonthsInCalendarBetween(start.getTime(), end.getTime(), tz);
    }

    /**
     * <pre>
     * 获取两时刻相减的【年】数差值 end-start，要求end的日期必须要>=start的才能算是一年（支持平年/闰年的处理），要求【必须满24h才能算1day】，使用系统默认的时区
     * 例如start为2015-01-01 12:00:00则end必须要>=2016-01-01 12:00:00才能算是一年
     * </pre>
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static int getYearsBetween(Date start, Date end) {
        return getYearsBetween(start.getTime(), end.getTime());
    }

    /**
     * <pre>
     * 获取两时刻相减的【年】数差值 end-start，要求end的日期必须要>=start的才能算是一年（支持平年/闰年的处理），要求【必须满24h才能算1day】，使用指定的时区
     * 例如start为2015-01-01 12:00:00则end必须要>=2016-01-01 12:00:00才能算是一年
     * </pre>
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static int getYearsBetween(Date start, Date end, TimeZone tz) {
        return getYearsBetween(start.getTime(), end.getTime(), tz);
    }

    /**
     * <pre>
     * 获取两时刻相减的【年】数差值 end-start，要求end的日期必须要>=start的才能算是一年（支持平年/闰年的处理），不要求一定要满24h，使用系统默认的时区
     * 例如start为2015-01-01 12:00:00则end只需>=2016-01-01 00:00:00就可以算是一年
     * </pre>
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static int getYearsInCalendarBetween(Date start, Date end) {
        return getYearsInCalendarBetween(start.getTime(), end.getTime());
    }

    /**
     * <pre>
     * 获取两时刻相减的【年】数差值 end-start，要求end的日期必须要>=start的才能算是一年（支持平年/闰年的处理），不要求一定要满24h，使用指定的时区
     * 例如start为2015-01-01 12:00:00则end只需>=2016-01-01 00:00:00就可以算是一年
     * </pre>
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static int getYearsInCalendarBetween(Date start, Date end, TimeZone tz) {
        return getYearsInCalendarBetween(start.getTime(), end.getTime(), tz);
    }

    /**
     * 获取两时刻相减的【毫秒】数差值 end-start
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static long getMillisBetween(String start, String end) {
        return getMillisBetween(parseSmartly(start), parseSmartly(end));
    }

    /**
     * 获取两时刻相减的【秒】数差值 end-start，将直接比较二者的时间差，>=1000ms才算是1s
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static long getSecondsBetween(String start, String end) {
        return getSecondsBetween(parseSmartly(start), parseSmartly(end));
    }

    /**
     * 获取两时刻相减的【分】数差值 end-start，将直接比较二者的时间差，>=60s才算是1min
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static long getMinutesBetween(String start, String end) {
        return getMinutesBetween(parseSmartly(start), parseSmartly(end));
    }

    /**
     * 获取两时刻相减的【小时】数差值 end-start，将直接比较二者的时间差，>=60min才算是1h
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static long getHoursBetween(String start, String end) {
        return getHoursBetween(parseSmartly(start), parseSmartly(end));
    }

    /**
     * 获取两时刻相减的【天】数差值 end-start，将直接比较二者的时间差，>=24h才算是1day
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static long getDaysBetween(String start, String end) {
        return getDaysBetween(parseSmartly(start), parseSmartly(end));
    }

    /**
     * 获取两时刻相减的【天】数差值 end-start，将比较二者的日期差，以自然天计算，日期相差1但差距不满24h的也可以算是1day，使用系统默认的时区
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static long getDaysInCalendarBetween(String start, String end) {
        return getDaysInCalendarBetween(parseSmartly(start), parseSmartly(end));
    }

    /**
     * 获取两时刻相减的【天】数差值 end-start，将比较二者的日期差，以自然天计算，日期相差1但差距不满24h的也可以算是1day，使用指定的时区
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static long getDaysInCalendarBetween(String start, String end, TimeZone tz) {
        return getDaysInCalendarBetween(parseSmartly(start), parseSmartly(end), tz);
    }

    /**
     * 获取两时刻相减的【周】数差值 end-start，必须要满足7*24h才能算是一周
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static long getWeeksBetween(String start, String end) {
        return getWeeksBetween(parseSmartly(start), parseSmartly(end));
    }

    /**
     * 获取两时刻相减的【周】数差值 end-start，将比较二者的日期差，以自然天7天计算，日期相差1但差距不满24h的也可以算是1day，使用系统默认的时区
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static long getWeeksInCalendarBetween(String start, String end) {
        return getWeeksInCalendarBetween(parseSmartly(start), parseSmartly(end));
    }

    /**
     * 获取两时刻相减的【周】数差值 end-start，将比较二者的日期差，以自然天7天计算，日期相差1但差距不满24h的也可以算是1day，使用指定的时区
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static long getWeeksInCalendarBetween(String start, String end, TimeZone tz) {
        return getWeeksInCalendarBetween(parseSmartly(start), parseSmartly(end), tz);
    }

    /**
     * <pre>
     * 获取两时刻相减的【月】数差值 end-start，要求end的日期必须要>=start的才能算是一个月（一个月天数可不固定，分大小月），要求【必须满24h才能算1day】，使用系统默认的时区
     * 例如start为2015-01-01 12:00:00则end必须要>=2015-02-01 12:00:00才能算是一个月
     * </pre>
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static int getMonthsBetween(String start, String end) {
        return getMonthsBetween(parseSmartly(start), parseSmartly(end));
    }

    /**
     * <pre>
     * 获取两时刻相减的【月】数差值 end-start，要求end的日期必须要>=start的才能算是一个月（一个月天数可不固定，分大小月），要求【必须满24h才能算1day】，使用指定的时区
     * 例如start为2015-01-01 12:00:00则end必须要>=2015-02-01 12:00:00才能算是一个月
     * </pre>
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static int getMonthsBetween(String start, String end, TimeZone tz) {
        return getMonthsBetween(parseSmartly(start), parseSmartly(end), tz);
    }

    /**
     * <pre>
     * 获取两时刻相减的【月】数差值 end-start，要求end的日期必须要>=start的才能算是一个月（一个月天数可不固定，分大小月），不要求一定要满24h，使用系统默认的时区
     * 例如start为2015-01-01 12:00:00则end只需>=2015-02-01 00:00:00就可以算是一个月
     * </pre>
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static int getMonthsInCalendarBetween(String start, String end) {
        return getMonthsInCalendarBetween(parseSmartly(start), parseSmartly(end));
    }

    /**
     * <pre>
     * 获取两时刻相减的【月】数差值 end-start，要求end的日期必须要>=start的才能算是一个月（一个月天数可不固定，分大小月），不要求一定要满24h，使用指定的时区
     * 例如start为2015-01-01 12:00:00则end只需>=2015-02-01 00:00:00就可以算是一个月
     * </pre>
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static int getMonthsInCalendarBetween(String start, String end, TimeZone tz) {
        return getMonthsInCalendarBetween(parseSmartly(start), parseSmartly(end), tz);
    }

    /**
     * <pre>
     * 获取两时刻相减的【年】数差值 end-start，要求end的日期必须要>=start的才能算是一年（支持平年/闰年的处理），要求【必须满24h才能算1day】，使用系统默认的时区
     * 例如start为2015-01-01 12:00:00则end必须要>=2016-01-01 12:00:00才能算是一年
     * </pre>
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static int getYearsBetween(String start, String end) {
        return getYearsBetween(parseSmartly(start), parseSmartly(end));
    }

    /**
     * <pre>
     * 获取两时刻相减的【年】数差值 end-start，要求end的日期必须要>=start的才能算是一年（支持平年/闰年的处理），要求【必须满24h才能算1day】，使用指定的时区
     * 例如start为2015-01-01 12:00:00则end必须要>=2016-01-01 12:00:00才能算是一年
     * </pre>
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static int getYearsBetween(String start, String end, TimeZone tz) {
        return getYearsBetween(parseSmartly(start), parseSmartly(end), tz);
    }

    /**
     * <pre>
     * 获取两时刻相减的【年】数差值 end-start，要求end的日期必须要>=start的才能算是一年（支持平年/闰年的处理），不要求一定要满24h，使用系统默认的时区
     * 例如start为2015-01-01 12:00:00则end只需>=2016-01-01 00:00:00就可以算是一年
     * </pre>
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static int getYearsInCalendarBetween(String start, String end) {
        return getYearsInCalendarBetween(parseSmartly(start), parseSmartly(end));
    }

    /**
     * <pre>
     * 获取两时刻相减的【年】数差值 end-start，要求end的日期必须要>=start的才能算是一年（支持平年/闰年的处理），不要求一定要满24h，使用指定的时区
     * 例如start为2015-01-01 12:00:00则end只需>=2016-01-01 00:00:00就可以算是一年
     * </pre>
     *
     * @return 如果end时刻在start之后，将返回正数的结果，如果end时刻在start之前则会返回负数的结果
     */
    public static int getYearsInCalendarBetween(String start, String end, TimeZone tz) {
        return getYearsInCalendarBetween(parseSmartly(start), parseSmartly(end), tz);
    }

    private static int _getDayCountOfMonth(int year, int month) {
        int days = DAYS_OF_MONTH[month];
        if (month != FEBRUARY) {
            return days;
        }
        // 如果是2月，判断该年是否为闰年
        if (_isLeapYear(year)) {
            return days + 1;
        }
        return days;
    }

    /**
     * 获取时间戳所在的月份有多少天，例如1月返回31天，使用系统默认时区
     */
    public static int getDayCountOfMonth(long timestamp) {
        return getDayCountOfMonth(timestamp, TimeZone.getDefault());
    }

    /**
     * 获取时间戳所在的月份有多少天，例如1月返回31天，使用指定的时区
     */
    public static int getDayCountOfMonth(long timestamp, TimeZone tz) {
        int year = DatetimeUtils.getYear(timestamp, tz);
        int month = DatetimeUtils.getMonthOfYear(timestamp, tz);
        return _getDayCountOfMonth(year, month);
    }

    /**
     * 获取时间戳所在的月份有多少天，例如1月返回31天，使用系统默认时区
     */
    public static int getDayCountOfMonth(Date date) {
        return getDayCountOfMonth(date, TimeZone.getDefault());
    }

    /**
     * 获取时间戳所在的月份有多少天，例如1月返回31天，使用指定的时区
     */
    public static int getDayCountOfMonth(Date date, TimeZone tz) {
        int year = DatetimeUtils.getYear(date, tz);
        int month = DatetimeUtils.getMonthOfYear(date, tz);
        return _getDayCountOfMonth(year, month);
    }

    /**
     * 获取时间戳所在的月份有多少天，例如1月返回31天，使用系统默认时区
     *
     * @param date 使用{@link #parseSmartly(String)}智能解析时间，如果解析失败返回0
     */
    public static int getDayCountOfMonth(String date) {
        return getDayCountOfMonth(date, TimeZone.getDefault());
    }

    /**
     * 获取时间戳所在的月份有多少天，例如1月返回31天，使用指定的时区
     *
     * @param date 使用{@link #parseSmartly(String)}智能解析时间，如果解析失败返回0
     */
    public static int getDayCountOfMonth(String date, TimeZone tz) {
        Date _date = parseSmartly(date);
        return null == _date ? 0 : getDayCountOfMonth(_date, tz);
    }

    /**
     * 判断时间戳所属的年份是否为闰年，使用系统默认的时区
     */
    public static boolean isLeapYear(long timestamp) {
        return isLeapYear(timestamp, TimeZone.getDefault());
    }

    private static boolean _isLeapYear(int year) {
        return (year % 100 != 0 && year % 4 == 0) || (year % 400 == 0);
    }

    /**
     * 判断时间戳所属的年份是否为闰年，使用指定的时区
     */
    public static boolean isLeapYear(long timestamp, TimeZone tz) {
        int year = DatetimeUtils.getYear(timestamp, tz);
        return _isLeapYear(year);
    }

    /**
     * 判断时间戳所属的年份是否为闰年，使用系统默认的时区
     */
    public static boolean isLeapYear(Date date) {
        return isLeapYear(date, TimeZone.getDefault());
    }

    /**
     * 判断时间戳所属的年份是否为闰年，使用指定的时区
     */
    public static boolean isLeapYear(Date date, TimeZone tz) {
        int year = DatetimeUtils.getYear(date, tz);
        return _isLeapYear(year);
    }

    /**
     * 判断时间戳所属的年份是否为闰年，使用系统默认的时区
     *
     * @param date 使用{@link #parseSmartly(String)}智能解析时间，如果解析失败返回false
     */
    public static boolean isLeapYear(String date) {
        return isLeapYear(date, TimeZone.getDefault());
    }

    /**
     * 判断时间戳所属的年份是否为闰年，使用指定的时区
     *
     * @param date 使用{@link #parseSmartly(String)}智能解析时间，如果解析失败返回false
     */
    public static boolean isLeapYear(String date, TimeZone tz) {
        Date _date = parseSmartly(date);
        if (null == _date) {
            return false;
        }
        int year = getYear(_date, tz);
        return _isLeapYear(year);
    }

    /**
     * 返回指定的日期是这一年的第几周，从周一开始算
     * （模仿前端代码实现）
     * <p>
     * 跨年周（即1月1日所处的那周）的归属原则为跨年周的周四处于哪年，则该跨年周属于哪年
     * 例如，2016-01-01的那周的周四（2015-12-31）在2015年，那么2016-01-01对应的年周为201553
     * 2014-01-01的那周的周四（2014-01-02）在2014年，那么2013-12-31对应的年周为201401
     */
    public static int getWeekId(Date date) {
        if (null == date) { // 如果没有传日期，默认以当前日期传参
            date = now();
        }
        Date datetime = parseSmartly(format(date, DF_yyyy_MM_dd));
        // 如果这一年的1月1号是属于上一年的最后1周，则需要加1周作为这一年的1一周起点
        Date yearStart = retainYear(datetime);
        int week = getDayOfWeek(yearStart);
        if (week <= THURSDAY) { // 1月1号是周四或周四以前，这一周就算是本年的
            yearStart = retainWeek(yearStart);
        } else {
            yearStart = plusWeeks(retainWeek(yearStart), 1);
        }
        if (isBefore(datetime, yearStart)) { // 如果算作是上一年的最后一周，就用递归方式计算
            return getWeekId(retainWeek(datetime));
        }
        return (int) (getYear(datetime) * 100 + getWeeksInCalendarBetween(yearStart, datetime) + 1);
    }

    /**
     * 根据weekId返回这一周的周一对应的日期
     * <p>
     * weekId生成的规则参考{@link #getWeekId(Date)}
     */
    public static Date getWeekStartDate(int weekId) {
        int weekOrder = weekId % 100;
        if (weekOrder > 53) {
            throw new IllegalArgumentException("Invalid weekId, a year cannot exceed 53 weeks");
        }
        Date yearStart = DatetimeUtils.parseSmartly(weekId / 100 + "-01-01");
        // 如果这一年的1月1号是属于上一年的最后1周，则需要加1周作为这一年的1一周起点
        int week = getDayOfWeek(yearStart);
        if (week <= THURSDAY) { // 1月1号是周四或周四以前，这一周就算是本年的
            yearStart = retainWeek(yearStart);
        } else {
            yearStart = plusWeeks(retainWeek(yearStart), 1);
        }
        return plusWeeks(yearStart, weekOrder - 1);
    }
}
